mirror of
https://github.com/bitwarden/server.git
synced 2025-04-14 09:38:16 -05:00
Added Secrets Manager Validations and Tests.
This commit is contained in:
parent
bf8d6fb3ba
commit
fcaa449f83
@ -18,4 +18,5 @@ public static class InviteUserValidationErrorMessages
|
||||
public const string OrganizationNoSecretsManager = "Organization has no access to Secrets Manager";
|
||||
public const string SecretsManagerSeatLimitReached = "Secrets Manager seat limit has been reached.";
|
||||
public const string SecretsManagerCannotExceedPasswordManager = "You cannot have more Secrets Manager seats than Password Manager seats.";
|
||||
public const string SecretsManagerAdditionalSeatLimitReached = "You have reached the maximum number of Secrets Manager seats ({0}) for this plan.";
|
||||
}
|
||||
|
@ -23,25 +23,34 @@ public class PasswordManagerSubscriptionUpdate
|
||||
|
||||
public int? UpdatedSeatTotal => Seats + SeatsRequiredToAdd;
|
||||
|
||||
public Plan Plan { get; }
|
||||
public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; }
|
||||
|
||||
private PasswordManagerSubscriptionUpdate(int? organizationSeats, int? organizationAutoScaleSeatLimit, int currentSeats, int seatsToAdd, Plan plan)
|
||||
private PasswordManagerSubscriptionUpdate(int? organizationSeats,
|
||||
int? organizationAutoScaleSeatLimit,
|
||||
int currentSeats,
|
||||
int seatsToAdd,
|
||||
Plan.PasswordManagerPlanFeatures plan)
|
||||
{
|
||||
Seats = organizationSeats;
|
||||
MaxAutoScaleSeats = organizationAutoScaleSeatLimit;
|
||||
OccupiedSeats = currentSeats;
|
||||
AdditionalSeats = seatsToAdd;
|
||||
Plan = plan;
|
||||
PasswordManagerPlan = plan;
|
||||
}
|
||||
|
||||
public static PasswordManagerSubscriptionUpdate Create(OrganizationDto organizationDto, int occupiedSeats, int seatsToAdd)
|
||||
{
|
||||
return new PasswordManagerSubscriptionUpdate(organizationDto.Seats, organizationDto.MaxAutoScaleSeats, occupiedSeats, seatsToAdd, organizationDto.Plan);
|
||||
return new PasswordManagerSubscriptionUpdate(
|
||||
organizationDto.Seats,
|
||||
organizationDto.MaxAutoScaleSeats,
|
||||
occupiedSeats,
|
||||
seatsToAdd,
|
||||
organizationDto.Plan.PasswordManager);
|
||||
}
|
||||
|
||||
public static PasswordManagerSubscriptionUpdate Create(InviteUserOrganizationValidationRequest refined)
|
||||
{
|
||||
return new PasswordManagerSubscriptionUpdate(refined.Organization.Seats, refined.Organization.MaxAutoScaleSeats,
|
||||
refined.OccupiedPmSeats, refined.Invites.Length, refined.Organization.Plan);
|
||||
refined.OccupiedPmSeats, refined.Invites.Length, refined.Organization.Plan.PasswordManager);
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,46 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
|
||||
|
||||
public class SecretsManagerSubscriptionUpdate
|
||||
{
|
||||
public bool UseSecretsManger { get; private init; }
|
||||
public int? Seats { get; private init; }
|
||||
public int? MaxAutoScaleSeats { get; private init; }
|
||||
public int OccupiedSeats { get; private init; }
|
||||
public int AdditionalSeats { get; private init; }
|
||||
public PasswordManagerSubscriptionUpdate PasswordManagerSubscriptionUpdate { get; private init; }
|
||||
public bool UseSecretsManger { get; }
|
||||
public int? Seats { get; }
|
||||
public int? MaxAutoScaleSeats { get; }
|
||||
public int OccupiedSeats { get; }
|
||||
public int AdditionalSeats { get; }
|
||||
public int? PasswordManagerUpdatedSeatTotal { get; }
|
||||
public Plan.SecretsManagerPlanFeatures SecretsManagerPlan { get; }
|
||||
public int? AvailableSeats => Seats - OccupiedSeats;
|
||||
public int SeatsRequiredToAdd => AdditionalSeats - AvailableSeats ?? 0;
|
||||
public int? UpdatedSeatTotal => Seats + SeatsRequiredToAdd;
|
||||
|
||||
private SecretsManagerSubscriptionUpdate(bool useSecretsManger, int? organizationSeats,
|
||||
int? organizationAutoScaleSeatLimit, int currentSeats, int seatsToAdd, PasswordManagerSubscriptionUpdate passwordManagerSeats)
|
||||
private SecretsManagerSubscriptionUpdate(bool useSecretsManger,
|
||||
int? organizationSeats,
|
||||
int? organizationAutoScaleSeatLimit,
|
||||
int currentSeats,
|
||||
int seatsToAdd,
|
||||
int? passwordManagerUpdatedSeatTotal,
|
||||
Plan.SecretsManagerPlanFeatures plan)
|
||||
{
|
||||
UseSecretsManger = useSecretsManger;
|
||||
Seats = organizationSeats;
|
||||
MaxAutoScaleSeats = organizationAutoScaleSeatLimit;
|
||||
OccupiedSeats = currentSeats;
|
||||
AdditionalSeats = seatsToAdd;
|
||||
PasswordManagerSubscriptionUpdate = passwordManagerSeats;
|
||||
PasswordManagerUpdatedSeatTotal = passwordManagerUpdatedSeatTotal;
|
||||
SecretsManagerPlan = plan;
|
||||
}
|
||||
|
||||
public static SecretsManagerSubscriptionUpdate Create(InviteUserOrganizationValidationRequest refined, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate)
|
||||
{
|
||||
return new SecretsManagerSubscriptionUpdate(refined.Organization.UseSecretsManager,
|
||||
refined.Organization.SmSeats, refined.Organization.SmMaxAutoScaleSeats,
|
||||
refined.OccupiedPmSeats, refined.Invites.Count(x => x.AccessSecretsManager),
|
||||
passwordManagerSubscriptionUpdate);
|
||||
refined.Organization.SmSeats,
|
||||
refined.Organization.SmMaxAutoScaleSeats,
|
||||
refined.OccupiedSmSeats,
|
||||
refined.Invites.Count(x => x.AccessSecretsManager),
|
||||
passwordManagerSubscriptionUpdate.UpdatedSeatTotal,
|
||||
refined.Organization.Plan.SecretsManager);
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,16 @@ public static class PasswordManagerInviteUserValidation
|
||||
return new Invalid<PasswordManagerSubscriptionUpdate>(SeatLimitHasBeenReachedError);
|
||||
}
|
||||
|
||||
if (subscriptionUpdate.Plan.PasswordManager.HasAdditionalSeatsOption is false)
|
||||
if (subscriptionUpdate.PasswordManagerPlan.HasAdditionalSeatsOption is false)
|
||||
{
|
||||
return new Invalid<PasswordManagerSubscriptionUpdate>(PlanDoesNotAllowAdditionalSeats);
|
||||
}
|
||||
|
||||
// Apparently MaxAdditionalSeats is never set. Can probably be removed.
|
||||
if (subscriptionUpdate.AdditionalSeats > subscriptionUpdate.Plan.PasswordManager.MaxAdditionalSeats)
|
||||
if (subscriptionUpdate.AdditionalSeats > subscriptionUpdate.PasswordManagerPlan.MaxAdditionalSeats)
|
||||
{
|
||||
return new Invalid<PasswordManagerSubscriptionUpdate>(string.Format(PlanOnlyAllowsMaxAdditionalSeats,
|
||||
subscriptionUpdate.Plan.PasswordManager.MaxAdditionalSeats));
|
||||
subscriptionUpdate.PasswordManagerPlan.MaxAdditionalSeats));
|
||||
}
|
||||
|
||||
return new Valid<PasswordManagerSubscriptionUpdate>(subscriptionUpdate);
|
||||
|
@ -3,12 +3,12 @@ using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Invite
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
|
||||
public class SecretsManagerInviteUserValidation
|
||||
public static class SecretsManagerInviteUserValidation
|
||||
{
|
||||
// Do we need to check if they are attempting to subtract seats? (no I don't think so because this is for inviting a User)
|
||||
// NOTE This is only validating adding new users
|
||||
public static ValidationResult<SecretsManagerSubscriptionUpdate> Validate(SecretsManagerSubscriptionUpdate subscriptionUpdate)
|
||||
{
|
||||
if (subscriptionUpdate.UseSecretsManger)
|
||||
if (subscriptionUpdate.UseSecretsManger is false)
|
||||
{
|
||||
return new Invalid<SecretsManagerSubscriptionUpdate>(OrganizationNoSecretsManager);
|
||||
}
|
||||
@ -18,63 +18,24 @@ public class SecretsManagerInviteUserValidation
|
||||
return new Valid<SecretsManagerSubscriptionUpdate>(subscriptionUpdate); // no need to adjust seats...continue on
|
||||
}
|
||||
|
||||
// if (update.Autoscaling && update.SmSeats.Value < organization.SmSeats.Value)
|
||||
// {
|
||||
// throw new BadRequestException("Cannot use autoscaling to subtract seats.");
|
||||
// }
|
||||
// max additional seats is never set...maybe remove this
|
||||
if (subscriptionUpdate.SecretsManagerPlan is { HasAdditionalSeatsOption: false } ||
|
||||
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats is not null &&
|
||||
subscriptionUpdate.AdditionalSeats > subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats)
|
||||
{
|
||||
return new Invalid<SecretsManagerSubscriptionUpdate>(
|
||||
string.Format(SecretsManagerAdditionalSeatLimitReached,
|
||||
subscriptionUpdate.SecretsManagerPlan.BaseSeats +
|
||||
subscriptionUpdate.SecretsManagerPlan.MaxAdditionalSeats.GetValueOrDefault()));
|
||||
}
|
||||
|
||||
// Might need to check plan
|
||||
|
||||
// Check plan maximum seats
|
||||
// if (!plan.SecretsManager.HasAdditionalSeatsOption ||
|
||||
// (plan.SecretsManager.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.SecretsManager.MaxAdditionalSeats.Value))
|
||||
// {
|
||||
// var planMaxSeats = plan.SecretsManager.BaseSeats + plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault();
|
||||
// throw new BadRequestException($"You have reached the maximum number of Secrets Manager seats ({planMaxSeats}) for this plan.");
|
||||
// }
|
||||
|
||||
// Check autoscale maximum seats
|
||||
if (subscriptionUpdate.UpdatedSeatTotal is not null && subscriptionUpdate.MaxAutoScaleSeats is not null &&
|
||||
subscriptionUpdate.UpdatedSeatTotal > subscriptionUpdate.MaxAutoScaleSeats)
|
||||
{
|
||||
return new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerSeatLimitReached);
|
||||
}
|
||||
|
||||
// if (update.MaxAutoscaleSmSeats.HasValue && update.SmSeats.Value > update.MaxAutoscaleSmSeats.Value)
|
||||
// {
|
||||
// var message = update.Autoscaling
|
||||
// ? "Secrets Manager seat limit has been reached."
|
||||
// : "Cannot set max seat autoscaling below seat count.";
|
||||
// throw new BadRequestException(message);
|
||||
// }
|
||||
|
||||
// Inviting a user... this shouldn't matter
|
||||
//
|
||||
// Check minimum seats included with plan
|
||||
// if (plan.SecretsManager.BaseSeats > update.SmSeats.Value)
|
||||
// {
|
||||
// throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseSeats} Secrets Manager seats.");
|
||||
// }
|
||||
|
||||
// Check minimum seats required by business logic
|
||||
// if (update.SmSeats.Value <= 0)
|
||||
// {
|
||||
// throw new BadRequestException("You must have at least 1 Secrets Manager seat.");
|
||||
// }
|
||||
|
||||
// Check minimum seats currently in use by the organization
|
||||
// if (organization.SmSeats.Value > update.SmSeats.Value)
|
||||
// {
|
||||
// var occupiedSeats = await _organizationUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id);
|
||||
// if (occupiedSeats > update.SmSeats.Value)
|
||||
// {
|
||||
// throw new BadRequestException($"{occupiedSeats} users are currently occupying Secrets Manager seats. " +
|
||||
// "You cannot decrease your subscription below your current occupied seat count.");
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check that SM seats aren't greater than password manager seats
|
||||
if (subscriptionUpdate.PasswordManagerSubscriptionUpdate.UpdatedSeatTotal < subscriptionUpdate.UpdatedSeatTotal)
|
||||
if (subscriptionUpdate.PasswordManagerUpdatedSeatTotal < subscriptionUpdate.UpdatedSeatTotal)
|
||||
{
|
||||
return new Invalid<SecretsManagerSubscriptionUpdate>(SecretsManagerCannotExceedPasswordManager);
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ public class PasswordManagerInviteUserValidationTests
|
||||
{
|
||||
organization.Seats = 4;
|
||||
organization.MaxAutoscaleSeats = 4;
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
var seatsOccupiedByUsers = 4;
|
||||
var additionalSeats = 1;
|
||||
|
||||
@ -81,5 +82,4 @@ public class PasswordManagerInviteUserValidationTests
|
||||
Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result);
|
||||
Assert.Equal(InviteUserValidationErrorMessages.PlanDoesNotAllowAdditionalSeats, result.ErrorMessageString);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,134 @@
|
||||
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;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SecretsManagerInviteUserValidationTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Validate_GivenOrganizationDoesNotHaveSecretsManager_ThenShouldNotBeAllowedToAddSecretsManagerUsers(
|
||||
Organization organization)
|
||||
{
|
||||
organization.UseSecretsManager = false;
|
||||
|
||||
var organizationDto = OrganizationDto.FromOrganization(organization);
|
||||
var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0);
|
||||
|
||||
var request = new InviteUserOrganizationValidationRequest
|
||||
{
|
||||
Invites = [new OrganizationUserInviteDto()],
|
||||
Organization = organizationDto,
|
||||
PerformedBy = Guid.Empty,
|
||||
PerformedAt = default,
|
||||
OccupiedPmSeats = 0,
|
||||
OccupiedSmSeats = 0
|
||||
};
|
||||
|
||||
var update = SecretsManagerSubscriptionUpdate.Create(request, subscriptionUpdate);
|
||||
|
||||
var result = SecretsManagerInviteUserValidation.Validate(update);
|
||||
|
||||
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
|
||||
Assert.Equal(InviteUserValidationErrorMessages.OrganizationNoSecretsManager, result.ErrorMessageString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Validate_GivenOrganizationHasSecretsManagerWithoutASeatLimit_ThenShouldBeAllowedToAddSecretsManagerUsers(
|
||||
Organization organization)
|
||||
{
|
||||
organization.SmSeats = null;
|
||||
organization.UseSecretsManager = true;
|
||||
|
||||
var organizationDto = OrganizationDto.FromOrganization(organization);
|
||||
var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0);
|
||||
|
||||
var request = new InviteUserOrganizationValidationRequest
|
||||
{
|
||||
Invites = [new OrganizationUserInviteDto()],
|
||||
Organization = organizationDto,
|
||||
PerformedBy = Guid.Empty,
|
||||
PerformedAt = default,
|
||||
OccupiedPmSeats = 0,
|
||||
OccupiedSmSeats = 0
|
||||
};
|
||||
|
||||
var update = SecretsManagerSubscriptionUpdate.Create(request, subscriptionUpdate);
|
||||
|
||||
var result = SecretsManagerInviteUserValidation.Validate(update);
|
||||
|
||||
Assert.IsType<Valid<SecretsManagerSubscriptionUpdate>>(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Validate_GivenOrganizationPlanDoesNotAllowAdditionalSeats_ThenShouldNotBeAllowedToAddSecretsManagerUsers(
|
||||
Organization organization)
|
||||
{
|
||||
organization.SmSeats = 4;
|
||||
organization.MaxAutoscaleSmSeats = 4;
|
||||
organization.UseSecretsManager = true;
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
var organizationDto = OrganizationDto.FromOrganization(organization);
|
||||
var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0);
|
||||
|
||||
var request = new InviteUserOrganizationValidationRequest
|
||||
{
|
||||
Invites = [OrganizationUserInviteDto.Create("email@test.com", OrganizationUserInvite.Create(["email@test.com"], [], OrganizationUserType.User, new Permissions(), string.Empty, true))],
|
||||
Organization = organizationDto,
|
||||
PerformedBy = Guid.Empty,
|
||||
PerformedAt = default,
|
||||
OccupiedPmSeats = 0,
|
||||
OccupiedSmSeats = 4
|
||||
};
|
||||
|
||||
var update = SecretsManagerSubscriptionUpdate.Create(request, subscriptionUpdate);
|
||||
|
||||
var result = SecretsManagerInviteUserValidation.Validate(update);
|
||||
|
||||
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
|
||||
Assert.Equal(InviteUserValidationErrorMessages.SecretsManagerSeatLimitReached, result.ErrorMessageString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void Validate_GivenPasswordManagerSeatsAreTheSameAsSecretsManagerSeats_WhenAttemptingToAddASecretManagerSeatOnly_ThenShouldNotBeAllowedToAddSecretsManagerUsers(
|
||||
Organization organization)
|
||||
{
|
||||
organization.SmSeats = 4;
|
||||
organization.MaxAutoscaleSmSeats = 5;
|
||||
organization.UseSecretsManager = true;
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
var organizationDto = OrganizationDto.FromOrganization(organization);
|
||||
var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0);
|
||||
|
||||
var request = new InviteUserOrganizationValidationRequest
|
||||
{
|
||||
Invites = [OrganizationUserInviteDto.Create("email@test.com", OrganizationUserInvite.Create(["email@test.com"], [], OrganizationUserType.User, new Permissions(), string.Empty, true))],
|
||||
Organization = organizationDto,
|
||||
PerformedBy = Guid.Empty,
|
||||
PerformedAt = default,
|
||||
OccupiedPmSeats = 0,
|
||||
OccupiedSmSeats = 4
|
||||
};
|
||||
|
||||
var update = SecretsManagerSubscriptionUpdate.Create(request, subscriptionUpdate);
|
||||
|
||||
var result = SecretsManagerInviteUserValidation.Validate(update);
|
||||
|
||||
Assert.IsType<Invalid<SecretsManagerSubscriptionUpdate>>(result);
|
||||
Assert.Equal(InviteUserValidationErrorMessages.SecretsManagerCannotExceedPasswordManager, result.ErrorMessageString);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user