2023-08-05 07:51:12 +10:00
|
|
|
|
using Bit.Core.Entities;
|
2023-07-24 23:05:05 +01:00
|
|
|
|
using Bit.Core.Enums;
|
|
|
|
|
|
using Bit.Core.Exceptions;
|
|
|
|
|
|
using Bit.Core.Models.Business;
|
|
|
|
|
|
using Bit.Core.Models.StaticStore;
|
|
|
|
|
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
|
|
|
|
|
using Bit.Core.Repositories;
|
|
|
|
|
|
using Bit.Core.SecretsManager.Repositories;
|
|
|
|
|
|
using Bit.Core.Services;
|
2023-08-05 07:51:12 +10:00
|
|
|
|
using Bit.Core.Settings;
|
2023-07-24 23:05:05 +01:00
|
|
|
|
using Bit.Core.Utilities;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
|
|
|
|
|
|
|
|
|
|
|
public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubscriptionCommand
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
|
|
|
|
|
private readonly IPaymentService _paymentService;
|
|
|
|
|
|
private readonly IMailService _mailService;
|
|
|
|
|
|
private readonly ILogger<UpdateSecretsManagerSubscriptionCommand> _logger;
|
|
|
|
|
|
private readonly IServiceAccountRepository _serviceAccountRepository;
|
2023-08-05 07:51:12 +10:00
|
|
|
|
private readonly IGlobalSettings _globalSettings;
|
|
|
|
|
|
private readonly IOrganizationRepository _organizationRepository;
|
|
|
|
|
|
private readonly IApplicationCacheService _applicationCacheService;
|
|
|
|
|
|
private readonly IEventService _eventService;
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
|
|
|
|
|
public UpdateSecretsManagerSubscriptionCommand(
|
|
|
|
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
|
|
|
|
IPaymentService paymentService,
|
|
|
|
|
|
IMailService mailService,
|
|
|
|
|
|
ILogger<UpdateSecretsManagerSubscriptionCommand> logger,
|
2023-08-05 07:51:12 +10:00
|
|
|
|
IServiceAccountRepository serviceAccountRepository,
|
|
|
|
|
|
IGlobalSettings globalSettings,
|
|
|
|
|
|
IOrganizationRepository organizationRepository,
|
|
|
|
|
|
IApplicationCacheService applicationCacheService,
|
|
|
|
|
|
IEventService eventService)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
_organizationUserRepository = organizationUserRepository;
|
|
|
|
|
|
_paymentService = paymentService;
|
|
|
|
|
|
_mailService = mailService;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_serviceAccountRepository = serviceAccountRepository;
|
2023-08-05 07:51:12 +10:00
|
|
|
|
_globalSettings = globalSettings;
|
|
|
|
|
|
_organizationRepository = organizationRepository;
|
|
|
|
|
|
_applicationCacheService = applicationCacheService;
|
|
|
|
|
|
_eventService = eventService;
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
public async Task UpdateSubscriptionAsync(SecretsManagerSubscriptionUpdate update)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await ValidateUpdate(update);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await FinalizeSubscriptionAdjustmentAsync(update.Organization, update.Plan, update);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await SendEmailIfAutoscaleLimitReached(update.Organization);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
public async Task AdjustServiceAccountsAsync(Organization organization, int smServiceAccountsAdjustment)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var update = new SecretsManagerSubscriptionUpdate(
|
|
|
|
|
|
organization, seatAdjustment: 0, maxAutoscaleSeats: organization?.MaxAutoscaleSmSeats,
|
|
|
|
|
|
serviceAccountAdjustment: smServiceAccountsAdjustment, maxAutoscaleServiceAccounts: organization?.MaxAutoscaleSmServiceAccounts)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
Autoscaling = true
|
|
|
|
|
|
};
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await UpdateSubscriptionAsync(update);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task FinalizeSubscriptionAdjustmentAsync(Organization organization,
|
|
|
|
|
|
Plan plan, SecretsManagerSubscriptionUpdate update)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (update.SmSeatsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ProcessChargesAndRaiseEventsForAdjustSeatsAsync(organization, plan, update);
|
|
|
|
|
|
organization.SmSeats = update.SmSeats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (update.SmServiceAccountsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ProcessChargesAndRaiseEventsForAdjustServiceAccountsAsync(organization, plan, update);
|
|
|
|
|
|
organization.SmServiceAccounts = update.SmServiceAccounts;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (update.MaxAutoscaleSmSeatsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
organization.MaxAutoscaleSmSeats = update.MaxAutoscaleSmSeats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (update.MaxAutoscaleSmServiceAccountsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
organization.MaxAutoscaleSmServiceAccounts = update.MaxAutoscaleSmServiceAccounts;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await ReplaceAndUpdateCacheAsync(organization);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ProcessChargesAndRaiseEventsForAdjustSeatsAsync(Organization organization, Plan plan,
|
|
|
|
|
|
SecretsManagerSubscriptionUpdate update)
|
|
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
await _paymentService.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase, update.ProrationDate);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
|
|
|
|
|
// TODO: call ReferenceEventService - see AC-1481
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ProcessChargesAndRaiseEventsForAdjustServiceAccountsAsync(Organization organization, Plan plan,
|
|
|
|
|
|
SecretsManagerSubscriptionUpdate update)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _paymentService.AdjustServiceAccountsAsync(organization, plan,
|
2023-08-05 07:51:12 +10:00
|
|
|
|
update.SmServiceAccountsExcludingBase, update.ProrationDate);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
|
|
|
|
|
// TODO: call ReferenceEventService - see AC-1481
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SendEmailIfAutoscaleLimitReached(Organization organization)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (organization.SmSeats.HasValue && organization.MaxAutoscaleSmSeats.HasValue && organization.SmSeats == organization.MaxAutoscaleSmSeats)
|
|
|
|
|
|
{
|
|
|
|
|
|
await SendSeatLimitEmailAsync(organization, organization.MaxAutoscaleSmSeats.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (organization.SmServiceAccounts.HasValue && organization.MaxAutoscaleSmServiceAccounts.HasValue && organization.SmServiceAccounts == organization.MaxAutoscaleSmServiceAccounts)
|
|
|
|
|
|
{
|
|
|
|
|
|
await SendServiceAccountLimitEmailAsync(organization, organization.MaxAutoscaleSmServiceAccounts.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SendSeatLimitEmailAsync(Organization organization, int MaxAutoscaleValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
|
|
|
|
|
OrganizationUserType.Owner))
|
|
|
|
|
|
.Select(u => u.Email).Distinct();
|
|
|
|
|
|
|
|
|
|
|
|
await _mailService.SendSecretsManagerMaxSeatLimitReachedEmailAsync(organization, MaxAutoscaleValue, ownerEmails);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, $"Error encountered notifying organization owners of Seats limit reached.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SendServiceAccountLimitEmailAsync(Organization organization, int MaxAutoscaleValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
|
|
|
|
|
|
OrganizationUserType.Owner))
|
|
|
|
|
|
.Select(u => u.Email).Distinct();
|
|
|
|
|
|
|
|
|
|
|
|
await _mailService.SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(organization, MaxAutoscaleValue, ownerEmails);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(e, $"Error encountered notifying organization owners of Service Accounts limit reached.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
public async Task ValidateUpdate(SecretsManagerSubscriptionUpdate update)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
if (_globalSettings.SelfHosted)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var message = update.Autoscaling
|
|
|
|
|
|
? "Cannot autoscale on a self-hosted instance."
|
|
|
|
|
|
: "Cannot update subscription on a self-hosted instance.";
|
|
|
|
|
|
throw new BadRequestException(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var organization = update.Organization;
|
|
|
|
|
|
ValidateOrganization(organization);
|
|
|
|
|
|
|
|
|
|
|
|
var plan = GetPlanForOrganization(organization);
|
|
|
|
|
|
|
|
|
|
|
|
if (update.SmSeatsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ValidateSmSeatsUpdateAsync(organization, update, plan);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (update.SmServiceAccountsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
await ValidateSmServiceAccountsUpdateAsync(organization, update, plan);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
if (update.MaxAutoscaleSmSeatsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
ValidateMaxAutoscaleSmSeatsUpdateAsync(organization, update.MaxAutoscaleSmSeats, plan);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (update.MaxAutoscaleSmServiceAccountsChanged)
|
|
|
|
|
|
{
|
|
|
|
|
|
ValidateMaxAutoscaleSmServiceAccountUpdate(organization, update.MaxAutoscaleSmServiceAccounts, plan);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ValidateOrganization(Organization organization)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (organization == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotFoundException("Organization is not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!organization.UseSecretsManager)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Organization has no access to Secrets Manager.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var plan = GetPlanForOrganization(organization);
|
|
|
|
|
|
if (plan.Product == ProductType.Free)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// No need to check the organization is set up with Stripe
|
|
|
|
|
|
return;
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("No payment method found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("No subscription found.");
|
|
|
|
|
|
}
|
2023-08-05 07:51:12 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Plan GetPlanForOrganization(Organization organization)
|
|
|
|
|
|
{
|
|
|
|
|
|
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
|
|
|
|
|
|
if (plan == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Existing plan not found.");
|
|
|
|
|
|
}
|
|
|
|
|
|
return plan;
|
|
|
|
|
|
}
|
2023-07-24 23:05:05 +01:00
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
private async Task ValidateSmSeatsUpdateAsync(Organization organization, SecretsManagerSubscriptionUpdate update, Plan plan)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check if the organization has unlimited seats
|
|
|
|
|
|
if (organization.SmSeats == null)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
throw new BadRequestException("Organization has no Secrets Manager seat limit, no need to adjust seats");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
if (update.Autoscaling && update.SmSeats.Value < organization.SmSeats.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
throw new BadRequestException("Cannot use autoscaling to subtract seats.");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check plan maximum seats
|
|
|
|
|
|
if (!plan.HasAdditionalSeatsOption ||
|
|
|
|
|
|
(plan.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.MaxAdditionalSeats.Value))
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var planMaxSeats = plan.BaseSeats + plan.MaxAdditionalSeats.GetValueOrDefault();
|
|
|
|
|
|
throw new BadRequestException($"You have reached the maximum number of Secrets Manager seats ({planMaxSeats}) for this plan.");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check autoscale maximum seats
|
|
|
|
|
|
if (update.MaxAutoscaleSmSeats.HasValue && update.SmSeats.Value > update.MaxAutoscaleSmSeats.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var message = update.Autoscaling
|
|
|
|
|
|
? "Secrets Manager seat limit has been reached."
|
|
|
|
|
|
: "Cannot set max seat autoscaling below seat count.";
|
|
|
|
|
|
throw new BadRequestException(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check minimum seats included with plan
|
|
|
|
|
|
if (plan.BaseSeats > update.SmSeats.Value)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Plan has a minimum of {plan.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.");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check minimum seats currently in use by the organization
|
|
|
|
|
|
if (organization.SmSeats.Value > update.SmSeats.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
var currentSeats = await _organizationUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id);
|
2023-08-05 07:51:12 +10:00
|
|
|
|
if (currentSeats > update.SmSeats.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Your organization currently has {currentSeats} Secrets Manager seats. " +
|
|
|
|
|
|
$"Your plan only allows {update.SmSeats} Secrets Manager seats. Remove some Secrets Manager users.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ValidateSmServiceAccountsUpdateAsync(Organization organization, SecretsManagerSubscriptionUpdate update, Plan plan)
|
|
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check if the organization has unlimited service accounts
|
2023-07-24 23:05:05 +01:00
|
|
|
|
if (organization.SmServiceAccounts == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Organization has no Service Accounts limit, no need to adjust Service Accounts");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
if (update.Autoscaling && update.SmServiceAccounts.Value < organization.SmServiceAccounts.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
throw new BadRequestException("Cannot use autoscaling to subtract service accounts.");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check plan maximum service accounts
|
|
|
|
|
|
if (!plan.HasAdditionalServiceAccountOption ||
|
|
|
|
|
|
(plan.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.MaxAdditionalServiceAccount.Value))
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var planMaxServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() +
|
|
|
|
|
|
plan.MaxAdditionalServiceAccount.GetValueOrDefault();
|
|
|
|
|
|
throw new BadRequestException($"You have reached the maximum number of service accounts ({planMaxServiceAccounts}) for this plan.");
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check autoscale maximum service accounts
|
|
|
|
|
|
if (update.MaxAutoscaleSmServiceAccounts.HasValue &&
|
|
|
|
|
|
update.SmServiceAccounts.Value > update.MaxAutoscaleSmServiceAccounts.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
2023-08-05 07:51:12 +10:00
|
|
|
|
var message = update.Autoscaling
|
|
|
|
|
|
? "Secrets Manager service account limit has been reached."
|
|
|
|
|
|
: "Cannot set max service accounts autoscaling below service account amount.";
|
|
|
|
|
|
throw new BadRequestException(message);
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check minimum service accounts included with plan
|
|
|
|
|
|
if (plan.BaseServiceAccount.HasValue && plan.BaseServiceAccount.Value > update.SmServiceAccounts.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Plan has a minimum of {plan.BaseServiceAccount} Service Accounts.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check minimum service accounts required by business logic
|
|
|
|
|
|
if (update.SmServiceAccounts.Value <= 0)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("You must have at least 1 Service Account.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-05 07:51:12 +10:00
|
|
|
|
// Check minimum service accounts currently in use by the organization
|
|
|
|
|
|
if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > update.SmServiceAccounts.Value)
|
2023-07-24 23:05:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
var currentServiceAccounts = await _serviceAccountRepository.GetServiceAccountCountByOrganizationIdAsync(organization.Id);
|
|
|
|
|
|
if (currentServiceAccounts > update.SmServiceAccounts)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Your organization currently has {currentServiceAccounts} Service Accounts. " +
|
|
|
|
|
|
$"Your plan only allows {update.SmServiceAccounts} Service Accounts. Remove some Service Accounts.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ValidateMaxAutoscaleSmSeatsUpdateAsync(Organization organization, int? maxAutoscaleSeats, Plan plan)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!maxAutoscaleSeats.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
// autoscale limit has been turned off, no validation required
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (organization.SmSeats.HasValue && maxAutoscaleSeats.Value < organization.SmSeats.Value)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException($"Cannot set max Secrets Manager seat autoscaling below current Secrets Manager seat count.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (plan.MaxUsers.HasValue && maxAutoscaleSeats.Value > plan.MaxUsers)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(string.Concat(
|
|
|
|
|
|
$"Your plan has a Secrets Manager seat limit of {plan.MaxUsers}, ",
|
|
|
|
|
|
$"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
|
|
|
|
|
|
"Reduce your max autoscale count."));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!plan.AllowSeatAutoscale)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your plan does not allow Secrets Manager seat autoscaling.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ValidateMaxAutoscaleSmServiceAccountUpdate(Organization organization, int? maxAutoscaleServiceAccounts, Plan plan)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!maxAutoscaleServiceAccounts.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
// autoscale limit has been turned off, no validation required
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (organization.SmServiceAccounts.HasValue && maxAutoscaleServiceAccounts.Value < organization.SmServiceAccounts.Value)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(
|
|
|
|
|
|
$"Cannot set max Service Accounts autoscaling below current Service Accounts count.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!plan.AllowServiceAccountsAutoscale)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException("Your plan does not allow Service Accounts autoscaling.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (plan.MaxServiceAccounts.HasValue && maxAutoscaleServiceAccounts.Value > plan.MaxServiceAccounts)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new BadRequestException(string.Concat(
|
|
|
|
|
|
$"Your plan has a Service Accounts limit of {plan.MaxServiceAccounts}, ",
|
|
|
|
|
|
$"but you have specified a max autoscale count of {maxAutoscaleServiceAccounts}.",
|
|
|
|
|
|
"Reduce your max autoscale count."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-08-05 07:51:12 +10:00
|
|
|
|
|
|
|
|
|
|
// TODO: This is a temporary duplication of OrganizationService.ReplaceAndUpdateCache to avoid a circular dependency.
|
|
|
|
|
|
// TODO: This should no longer be necessary when user-related methods are extracted from OrganizationService: see PM-1880
|
|
|
|
|
|
private async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _organizationRepository.ReplaceAsync(org);
|
|
|
|
|
|
await _applicationCacheService.UpsertOrganizationAbilityAsync(org);
|
|
|
|
|
|
|
|
|
|
|
|
if (orgEvent.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventService.LogOrganizationEventAsync(org, orgEvent.Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-07-24 23:05:05 +01:00
|
|
|
|
}
|