Browse Source

Block org seat scaling when has Reseller provider (#3385)

pull/3462/head
Alex Morask 2 years ago committed by GitHub
parent
commit
07c202ecaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      src/Core/Services/Implementations/OrganizationService.cs
  2. 29
      test/Core.Test/Services/OrganizationServiceTests.cs

17
src/Core/Services/Implementations/OrganizationService.cs

@ -57,6 +57,7 @@ public class OrganizationService : IOrganizationService
private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderUserRepository _providerUserRepository;
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery; private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand; private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IProviderRepository _providerRepository;
private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory; private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory;
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory; private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
@ -90,6 +91,7 @@ public class OrganizationService : IOrganizationService
IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory, IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory,
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory, IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand, IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IProviderRepository providerRepository,
IFeatureService featureService) IFeatureService featureService)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -118,6 +120,7 @@ public class OrganizationService : IOrganizationService
_providerUserRepository = providerUserRepository; _providerUserRepository = providerUserRepository;
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery; _countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand; _updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_providerRepository = providerRepository;
_orgUserInviteTokenableFactory = orgUserInviteTokenableFactory; _orgUserInviteTokenableFactory = orgUserInviteTokenableFactory;
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
_featureService = featureService; _featureService = featureService;
@ -862,7 +865,7 @@ public class OrganizationService : IOrganizationService
if (newSeatsRequired > 0) if (newSeatsRequired > 0)
{ {
var (canScale, failureReason) = CanScale(organization, newSeatsRequired); var (canScale, failureReason) = await CanScaleAsync(organization, newSeatsRequired);
if (!canScale) if (!canScale)
{ {
throw new BadRequestException(failureReason); throw new BadRequestException(failureReason);
@ -1182,7 +1185,8 @@ public class OrganizationService : IOrganizationService
return result; return result;
} }
internal (bool canScale, string failureReason) CanScale(Organization organization, internal async Task<(bool canScale, string failureReason)> CanScaleAsync(
Organization organization,
int seatsToAdd) int seatsToAdd)
{ {
var failureReason = ""; var failureReason = "";
@ -1197,6 +1201,13 @@ public class OrganizationService : IOrganizationService
return (true, failureReason); return (true, failureReason);
} }
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
if (provider is { Enabled: true, Type: ProviderType.Reseller })
{
return (false, "Seat limit has been reached. Contact your provider to purchase additional seats.");
}
if (organization.Seats.HasValue && if (organization.Seats.HasValue &&
organization.MaxAutoscaleSeats.HasValue && organization.MaxAutoscaleSeats.HasValue &&
organization.MaxAutoscaleSeats.Value < organization.Seats.Value + seatsToAdd) organization.MaxAutoscaleSeats.Value < organization.Seats.Value + seatsToAdd)
@ -1214,7 +1225,7 @@ public class OrganizationService : IOrganizationService
return; return;
} }
var (canScale, failureMessage) = CanScale(organization, seatsToAdd); var (canScale, failureMessage) = await CanScaleAsync(organization, seatsToAdd);
if (!canScale) if (!canScale)
{ {
throw new BadRequestException(failureMessage); throw new BadRequestException(failureMessage);

29
test/Core.Test/Services/OrganizationServiceTests.cs

@ -34,6 +34,7 @@ using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Fakes; using Bit.Test.Common.Fakes;
using NSubstitute; using NSubstitute;
using NSubstitute.ExceptionExtensions; using NSubstitute.ExceptionExtensions;
using NSubstitute.ReturnsExtensions;
using Xunit; using Xunit;
using Organization = Bit.Core.Entities.Organization; using Organization = Bit.Core.Entities.Organization;
using OrganizationUser = Bit.Core.Entities.OrganizationUser; using OrganizationUser = Bit.Core.Entities.OrganizationUser;
@ -1606,15 +1607,16 @@ public class OrganizationServiceTests
[BitAutoData(0, null, 100, true, "")] [BitAutoData(0, null, 100, true, "")]
[BitAutoData(1, 100, null, true, "")] [BitAutoData(1, 100, null, true, "")]
[BitAutoData(1, 100, 100, false, "Seat limit has been reached")] [BitAutoData(1, 100, 100, false, "Seat limit has been reached")]
public void CanScale(int seatsToAdd, int? currentSeats, int? maxAutoscaleSeats, public async Task CanScaleAsync(int seatsToAdd, int? currentSeats, int? maxAutoscaleSeats,
bool expectedResult, string expectedFailureMessage, Organization organization, bool expectedResult, string expectedFailureMessage, Organization organization,
SutProvider<OrganizationService> sutProvider) SutProvider<OrganizationService> sutProvider)
{ {
organization.Seats = currentSeats; organization.Seats = currentSeats;
organization.MaxAutoscaleSeats = maxAutoscaleSeats; organization.MaxAutoscaleSeats = maxAutoscaleSeats;
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true); sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).ReturnsNull();
var (result, failureMessage) = sutProvider.Sut.CanScale(organization, seatsToAdd); var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, seatsToAdd);
if (expectedFailureMessage == string.Empty) if (expectedFailureMessage == string.Empty)
{ {
@ -1628,16 +1630,35 @@ public class OrganizationServiceTests
} }
[Theory, PaidOrganizationCustomize, BitAutoData] [Theory, PaidOrganizationCustomize, BitAutoData]
public void CanScale_FailsOnSelfHosted(Organization organization, public async Task CanScaleAsync_FailsOnSelfHosted(Organization organization,
SutProvider<OrganizationService> sutProvider) SutProvider<OrganizationService> sutProvider)
{ {
sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true); sutProvider.GetDependency<IGlobalSettings>().SelfHosted.Returns(true);
var (result, failureMessage) = sutProvider.Sut.CanScale(organization, 10); var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
Assert.False(result); Assert.False(result);
Assert.Contains("Cannot autoscale on self-hosted instance", failureMessage); Assert.Contains("Cannot autoscale on self-hosted instance", failureMessage);
} }
[Theory, PaidOrganizationCustomize, BitAutoData]
public async Task CanScaleAsync_FailsOnResellerManagedOrganization(
Organization organization,
SutProvider<OrganizationService> sutProvider)
{
var provider = new Provider
{
Enabled = true,
Type = ProviderType.Reseller
};
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
var (result, failureMessage) = await sutProvider.Sut.CanScaleAsync(organization, 10);
Assert.False(result);
Assert.Contains("Seat limit has been reached. Contact your provider to purchase additional seats.", failureMessage);
}
[Theory, PaidOrganizationCustomize, BitAutoData] [Theory, PaidOrganizationCustomize, BitAutoData]
public async Task Delete_Success(Organization organization, SutProvider<OrganizationService> sutProvider) public async Task Delete_Success(Organization organization, SutProvider<OrganizationService> sutProvider)
{ {

Loading…
Cancel
Save