1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[AC-2461] Scale provider seats on client organization deletion (#3996)

* Scaled provider seats on client organization deletion

* Thomas' feedback
This commit is contained in:
Alex Morask
2024-04-19 10:09:18 -04:00
committed by GitHub
parent e6bd8779a6
commit 821f7620b6
4 changed files with 118 additions and 9 deletions

View File

@ -3,10 +3,12 @@ using Bit.Admin.AdminConsole.Models;
using Bit.Admin.Enums;
using Bit.Admin.Services;
using Bit.Admin.Utilities;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Providers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Extensions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -53,6 +55,8 @@ public class OrganizationsController : Controller
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand;
private readonly IRemovePaymentMethodCommand _removePaymentMethodCommand;
private readonly IFeatureService _featureService;
private readonly IScaleSeatsCommand _scaleSeatsCommand;
public OrganizationsController(
IOrganizationService organizationService,
@ -78,7 +82,9 @@ public class OrganizationsController : Controller
IServiceAccountRepository serviceAccountRepository,
IProviderOrganizationRepository providerOrganizationRepository,
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand,
IRemovePaymentMethodCommand removePaymentMethodCommand)
IRemovePaymentMethodCommand removePaymentMethodCommand,
IFeatureService featureService,
IScaleSeatsCommand scaleSeatsCommand)
{
_organizationService = organizationService;
_organizationRepository = organizationRepository;
@ -104,6 +110,8 @@ public class OrganizationsController : Controller
_providerOrganizationRepository = providerOrganizationRepository;
_removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand;
_removePaymentMethodCommand = removePaymentMethodCommand;
_featureService = featureService;
_scaleSeatsCommand = scaleSeatsCommand;
}
[RequirePermission(Permission.Org_List_View)]
@ -234,12 +242,30 @@ public class OrganizationsController : Controller
public async Task<IActionResult> Delete(Guid id)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization != null)
if (organization == null)
{
await _organizationRepository.DeleteAsync(organization);
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
return RedirectToAction("Index");
}
var consolidatedBillingEnabled = _featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling);
if (consolidatedBillingEnabled && organization.IsValidClient())
{
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
if (provider.IsBillable())
{
await _scaleSeatsCommand.ScalePasswordManagerSeats(
provider,
organization.PlanType,
-organization.Seats ?? 0);
}
}
await _organizationRepository.DeleteAsync(organization);
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
return RedirectToAction("Index");
}

View File

@ -20,6 +20,7 @@ using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.Services;
using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Models;
using Bit.Core.Billing.Queries;
using Bit.Core.Context;
@ -69,6 +70,8 @@ public class OrganizationsController : Controller
private readonly ISubscriberQueries _subscriberQueries;
private readonly IReferenceEventService _referenceEventService;
private readonly IOrganizationEnableCollectionEnhancementsCommand _organizationEnableCollectionEnhancementsCommand;
private readonly IProviderRepository _providerRepository;
private readonly IScaleSeatsCommand _scaleSeatsCommand;
public OrganizationsController(
IOrganizationRepository organizationRepository,
@ -95,7 +98,9 @@ public class OrganizationsController : Controller
ICancelSubscriptionCommand cancelSubscriptionCommand,
ISubscriberQueries subscriberQueries,
IReferenceEventService referenceEventService,
IOrganizationEnableCollectionEnhancementsCommand organizationEnableCollectionEnhancementsCommand)
IOrganizationEnableCollectionEnhancementsCommand organizationEnableCollectionEnhancementsCommand,
IProviderRepository providerRepository,
IScaleSeatsCommand scaleSeatsCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -122,6 +127,8 @@ public class OrganizationsController : Controller
_subscriberQueries = subscriberQueries;
_referenceEventService = referenceEventService;
_organizationEnableCollectionEnhancementsCommand = organizationEnableCollectionEnhancementsCommand;
_providerRepository = providerRepository;
_scaleSeatsCommand = scaleSeatsCommand;
}
[HttpGet("{id}")]
@ -560,10 +567,23 @@ public class OrganizationsController : Controller
await Task.Delay(2000);
throw new BadRequestException(string.Empty, "User verification failed.");
}
else
var consolidatedBillingEnabled = _featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling);
if (consolidatedBillingEnabled && organization.IsValidClient())
{
await _organizationService.DeleteAsync(organization);
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
if (provider.IsBillable())
{
await _scaleSeatsCommand.ScalePasswordManagerSeats(
provider,
organization.PlanType,
-organization.Seats ?? 0);
}
}
await _organizationService.DeleteAsync(organization);
}
[HttpPost("{id}/import")]

View File

@ -1,9 +1,27 @@
using Bit.Core.Enums;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.Enums;
namespace Bit.Core.Billing.Extensions;
public static class BillingExtensions
{
public static bool IsBillable(this Provider provider) =>
provider is
{
Type: ProviderType.Msp,
Status: ProviderStatusType.Billable
};
public static bool IsValidClient(this Organization organization)
=> organization is
{
Seats: not null,
Status: OrganizationStatusType.Managed,
PlanType: PlanType.TeamsMonthly or PlanType.EnterpriseMonthly
};
public static bool SupportsConsolidatedBilling(this PlanType planType)
=> planType is PlanType.TeamsMonthly or PlanType.EnterpriseMonthly;
}