1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

[AC-1486] Feature: SM Billing (#3073)

* [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem (#3037)

* [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem

* [AC-1423] Add helper to StaticStore.cs to find a Plan by StripePlanId

* [AC-1423] Use the helper method to set SubscriptionInfo.BitwardenProduct

* Add SecretsManagerBilling feature flag to Constants

* [AC 1409] Secrets Manager Subscription Stripe Integration  (#3019)

* [AC-1418] Add missing SecretsManagerPlan property to OrganizationResponseModel (#3055)

* [AC 1460] Update Stripe Configuration (#3070)

* [AC 1410] Secrets Manager subscription adjustment back-end changes (#3036)

* Create UpgradeSecretsManagerSubscription command

* [AC-1495] Extract UpgradePlanAsync into a command (#3081)

* This is a pure lift & shift with no refactors

* [AC-1503] Fix Stripe integration on organization upgrade (#3084)

* Fix SM parameters not being passed to Stripe

* [AC-1504] Allow SM max autoscale limits to be disabled (#3085)

* [AC-1488] Changed SM Signup and Upgrade paths to set SmServiceAccounts to include the plan BaseServiceAccount (#3086)

* [AC-1510] Enable access to Secrets Manager to Organization owner for new Subscription (#3089)

* Revert changes to ReferenceEvent code (#3091)

This will be done in AC-1481

* Add UsePasswordManager to sync data (#3114)

* [AC-1522] Fix service account check on upgrading (#3111)

* [AC-1521] Address checkmarx security feedback (#3124)

* Reinstate target attribute but add noopener noreferrer

* Update date on migration script

---------

Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: cyprain-okeke <cokeke@bitwarden.com>
Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com>
Co-authored-by: Rui Tome <rtome@bitwarden.com>
This commit is contained in:
Álison Fernandes
2023-07-24 23:05:05 +01:00
committed by GitHub
parent 4ec765ae19
commit 35111382e5
50 changed files with 2880 additions and 381 deletions

View File

@ -18,6 +18,7 @@ using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@ -50,6 +51,8 @@ public class OrganizationsController : Controller
private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
private readonly IUpgradeOrganizationPlanCommand _upgradeOrganizationPlanCommand;
public OrganizationsController(
IOrganizationRepository organizationRepository,
@ -70,7 +73,9 @@ public class OrganizationsController : Controller
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IFeatureService featureService,
GlobalSettings globalSettings,
ILicensingService licensingService)
ILicensingService licensingService,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -91,6 +96,8 @@ public class OrganizationsController : Controller
_featureService = featureService;
_globalSettings = globalSettings;
_licensingService = licensingService;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
_upgradeOrganizationPlanCommand = upgradeOrganizationPlanCommand;
}
[HttpGet("{id}")]
@ -306,7 +313,7 @@ public class OrganizationsController : Controller
throw new NotFoundException();
}
var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
var result = await _upgradeOrganizationPlanCommand.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 };
}
@ -319,10 +326,34 @@ public class OrganizationsController : Controller
{
throw new NotFoundException();
}
await _organizationService.UpdateSubscription(orgIdGuid, model.SeatAdjustment, model.MaxAutoscaleSeats);
}
[HttpPost("{id}/sm-subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostSmSubscription(Guid id, [FromBody] SecretsManagerSubscriptionUpdateRequestModel model)
{
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.EditSubscription(id))
{
throw new NotFoundException();
}
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
if (secretsManagerPlan == null)
{
throw new NotFoundException("Invalid Secrets Manager plan.");
}
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization, secretsManagerPlan);
await _updateSecretsManagerSubscriptionCommand.UpdateSecretsManagerSubscription(organizationUpdate);
}
[HttpPost("{id}/seat")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<PaymentResponseModel> PostSeat(string id, [FromBody] OrganizationSeatRequestModel model)

View File

@ -40,6 +40,12 @@ public class OrganizationCreateRequestModel : IValidatableObject
[StringLength(2)]
public string BillingAddressCountry { get; set; }
public int? MaxAutoscaleSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalSmSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalServiceAccounts { get; set; }
[Required]
public bool UseSecretsManager { get; set; }
public virtual OrganizationSignup ToOrganizationSignup(User user)
{
@ -58,6 +64,9 @@ public class OrganizationCreateRequestModel : IValidatableObject
BillingEmail = BillingEmail,
BusinessName = BusinessName,
CollectionName = CollectionName,
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(),
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(),
UseSecretsManager = UseSecretsManager,
TaxInfo = new TaxInfo
{
TaxIdNumber = TaxIdNumber,

View File

@ -13,6 +13,12 @@ public class OrganizationUpgradeRequestModel
public int AdditionalSeats { get; set; }
[Range(0, 99)]
public short? AdditionalStorageGb { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalSmSeats { get; set; }
[Range(0, int.MaxValue)]
public int? AdditionalServiceAccounts { get; set; }
[Required]
public bool UseSecretsManager { get; set; }
public bool PremiumAccessAddon { get; set; }
public string BillingAddressCountry { get; set; }
public string BillingAddressPostalCode { get; set; }
@ -24,6 +30,9 @@ public class OrganizationUpgradeRequestModel
{
AdditionalSeats = AdditionalSeats,
AdditionalStorageGb = AdditionalStorageGb.GetValueOrDefault(),
AdditionalServiceAccounts = AdditionalServiceAccounts.GetValueOrDefault(0),
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(0),
UseSecretsManager = UseSecretsManager,
BusinessName = BusinessName,
Plan = PlanType,
PremiumAccessAddon = PremiumAccessAddon,

View File

@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Models.Business;
using Bit.Core.Models.StaticStore;
namespace Bit.Api.Models.Request.Organizations;
public class SecretsManagerSubscriptionUpdateRequestModel
{
[Required]
public int SeatAdjustment { get; set; }
public int? MaxAutoscaleSeats { get; set; }
public int ServiceAccountAdjustment { get; set; }
public int? MaxAutoscaleServiceAccounts { get; set; }
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization, Plan plan)
{
var newTotalSeats = organization.SmSeats.GetValueOrDefault() + SeatAdjustment;
var newTotalServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault() + ServiceAccountAdjustment;
var orgUpdate = new SecretsManagerSubscriptionUpdate
{
OrganizationId = organization.Id,
SmSeatsAdjustment = SeatAdjustment,
SmSeats = newTotalSeats,
SmSeatsExcludingBase = newTotalSeats - plan.BaseSeats,
MaxAutoscaleSmSeats = MaxAutoscaleSeats,
SmServiceAccountsAdjustment = ServiceAccountAdjustment,
SmServiceAccounts = newTotalServiceAccounts,
SmServiceAccountsExcludingBase = newTotalServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault(),
MaxAutoscaleSmServiceAccounts = MaxAutoscaleServiceAccounts,
MaxAutoscaleSmSeatsChanged =
MaxAutoscaleSeats.GetValueOrDefault() != organization.MaxAutoscaleSmSeats.GetValueOrDefault(),
MaxAutoscaleSmServiceAccountsChanged =
MaxAutoscaleServiceAccounts.GetValueOrDefault() != organization.MaxAutoscaleSmServiceAccounts.GetValueOrDefault()
};
return orgUpdate;
}
}

View File

@ -27,6 +27,7 @@ public class OrganizationResponseModel : ResponseModel
BusinessTaxNumber = organization.BusinessTaxNumber;
BillingEmail = organization.BillingEmail;
Plan = new PlanResponseModel(StaticStore.PasswordManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
SecretsManagerPlan = new PlanResponseModel(StaticStore.SecretManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
PlanType = organization.PlanType;
Seats = organization.Seats;
MaxAutoscaleSeats = organization.MaxAutoscaleSeats;
@ -65,6 +66,7 @@ public class OrganizationResponseModel : ResponseModel
public string BusinessTaxNumber { get; set; }
public string BillingEmail { get; set; }
public PlanResponseModel Plan { get; set; }
public PlanResponseModel SecretsManagerPlan { get; set; }
public PlanType PlanType { get; set; }
public int? Seats { get; set; }
public int? MaxAutoscaleSeats { get; set; } = null;

View File

@ -55,10 +55,12 @@ public class PlanResponseModel : ResponseModel
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxServiceAccount = plan.MaxServiceAccount;
MaxServiceAccounts = plan.MaxServiceAccounts;
MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption;
MaxProjects = plan.MaxProjects;
BitwardenProduct = plan.BitwardenProduct;
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
}
public PlanType Type { get; set; }
@ -105,10 +107,11 @@ public class PlanResponseModel : ResponseModel
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }
public string StripeServiceAccountPlanId { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxServiceAccount { get; set; }
public short? MaxServiceAccounts { get; set; }
public short? MaxAdditionalServiceAccounts { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }

View File

@ -29,6 +29,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
UseApi = organization.UseApi;
UseResetPassword = organization.UseResetPassword;
UseSecretsManager = organization.UseSecretsManager;
UsePasswordManager = organization.UsePasswordManager;
UsersGetPremium = organization.UsersGetPremium;
UseCustomPermissions = organization.UseCustomPermissions;
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
@ -82,6 +83,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
public bool UseApi { get; set; }
public bool UseResetPassword { get; set; }
public bool UseSecretsManager { get; set; }
public bool UsePasswordManager { get; set; }
public bool UsersGetPremium { get; set; }
public bool UseCustomPermissions { get; set; }
public bool UseActivateAutofillPolicy { get; set; }

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
@ -82,6 +83,8 @@ public class BillingSubscription
Interval = item.Interval;
Quantity = item.Quantity;
SponsoredSubscriptionItem = item.SponsoredSubscriptionItem;
AddonSubscriptionItem = item.AddonSubscriptionItem;
BitwardenProduct = item.BitwardenProduct;
}
public string Name { get; set; }
@ -89,6 +92,8 @@ public class BillingSubscription
public int Quantity { get; set; }
public string Interval { get; set; }
public bool SponsoredSubscriptionItem { get; set; }
public bool AddonSubscriptionItem { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
}
}

View File

@ -15,6 +15,7 @@ using Bit.SharedWeb.Utilities;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Bit.Core.Auth.Identity;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
#if !OSS
using Bit.Commercial.Core.SecretsManager;
@ -133,6 +134,7 @@ public class Startup
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
services.AddOrganizationSubscriptionServices();
services.AddCoreLocalizationServices();
//health check