mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[PM-16684] Integrate Pricing Service behind FF (#5276)
* Remove gRPC and convert PricingClient to HttpClient wrapper * Add PlanType.GetProductTier extension Many instances of StaticStore use are just to get the ProductTierType of a PlanType, but this can be derived from the PlanType itself without having to fetch the entire plan. * Remove invocations of the StaticStore in non-Test code * Deprecate StaticStore entry points * Run dotnet format * Matt's feedback * Run dotnet format * Rui's feedback * Run dotnet format * Replacements since approval * Run dotnet format
This commit is contained in:
@ -13,6 +13,7 @@ using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -55,6 +56,7 @@ public class OrganizationUsersController : Controller
|
||||
private readonly IDeleteManagedOrganizationUserAccountCommand _deleteManagedOrganizationUserAccountCommand;
|
||||
private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public OrganizationUsersController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -77,7 +79,8 @@ public class OrganizationUsersController : Controller
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
IDeleteManagedOrganizationUserAccountCommand deleteManagedOrganizationUserAccountCommand,
|
||||
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
|
||||
IFeatureService featureService)
|
||||
IFeatureService featureService,
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -100,6 +103,7 @@ public class OrganizationUsersController : Controller
|
||||
_deleteManagedOrganizationUserAccountCommand = deleteManagedOrganizationUserAccountCommand;
|
||||
_getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery;
|
||||
_featureService = featureService;
|
||||
_pricingClient = pricingClient;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -648,7 +652,9 @@ public class OrganizationUsersController : Controller
|
||||
if (additionalSmSeatsRequired > 0)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, true)
|
||||
// TODO: https://bitwarden.atlassian.net/browse/PM-17000
|
||||
var plan = await _pricingClient.GetPlanOrThrow(organization!.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true)
|
||||
.AdjustSeats(additionalSmSeatsRequired);
|
||||
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
@ -60,6 +61,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -81,7 +83,8 @@ public class OrganizationsController : Controller
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||
IOrganizationDeleteCommand organizationDeleteCommand)
|
||||
IOrganizationDeleteCommand organizationDeleteCommand,
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -103,6 +106,7 @@ public class OrganizationsController : Controller
|
||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
||||
_organizationDeleteCommand = organizationDeleteCommand;
|
||||
_pricingClient = pricingClient;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -120,7 +124,8 @@ public class OrganizationsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return new OrganizationResponseModel(organization);
|
||||
var plan = await _pricingClient.GetPlan(organization.PlanType);
|
||||
return new OrganizationResponseModel(organization, plan);
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@ -181,7 +186,8 @@ public class OrganizationsController : Controller
|
||||
|
||||
var organizationSignup = model.ToOrganizationSignup(user);
|
||||
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Organization);
|
||||
var plan = await _pricingClient.GetPlanOrThrow(result.Organization.PlanType);
|
||||
return new OrganizationResponseModel(result.Organization, plan);
|
||||
}
|
||||
|
||||
[HttpPost("create-without-payment")]
|
||||
@ -196,7 +202,8 @@ public class OrganizationsController : Controller
|
||||
|
||||
var organizationSignup = model.ToOrganizationSignup(user);
|
||||
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Organization);
|
||||
var plan = await _pricingClient.GetPlanOrThrow(result.Organization.PlanType);
|
||||
return new OrganizationResponseModel(result.Organization, plan);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
@ -224,7 +231,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
await _organizationService.UpdateAsync(model.ToOrganization(organization, _globalSettings), updateBilling);
|
||||
return new OrganizationResponseModel(organization);
|
||||
var plan = await _pricingClient.GetPlan(organization.PlanType);
|
||||
return new OrganizationResponseModel(organization, plan);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/storage")]
|
||||
@ -358,8 +366,8 @@ public class OrganizationsController : Controller
|
||||
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim)
|
||||
{
|
||||
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types
|
||||
var plan = StaticStore.GetPlan(organization.PlanType);
|
||||
if (plan.ProductTier is not ProductTierType.Enterprise and not ProductTierType.Teams)
|
||||
var productTier = organization.PlanType.GetProductTier();
|
||||
if (productTier is not ProductTierType.Enterprise and not ProductTierType.Teams)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
@ -542,7 +550,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
await _organizationService.UpdateAsync(model.ToOrganization(organization, _featureService), eventType: EventType.Organization_CollectionManagement_Updated);
|
||||
return new OrganizationResponseModel(organization);
|
||||
var plan = await _pricingClient.GetPlan(organization.PlanType);
|
||||
return new OrganizationResponseModel(organization, plan);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/plan-type")]
|
||||
|
@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Utilities;
|
||||
using Constants = Bit.Core.Constants;
|
||||
|
||||
@ -11,8 +12,10 @@ namespace Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
|
||||
public class OrganizationResponseModel : ResponseModel
|
||||
{
|
||||
public OrganizationResponseModel(Organization organization, string obj = "organization")
|
||||
: base(obj)
|
||||
public OrganizationResponseModel(
|
||||
Organization organization,
|
||||
Plan plan,
|
||||
string obj = "organization") : base(obj)
|
||||
{
|
||||
if (organization == null)
|
||||
{
|
||||
@ -28,7 +31,8 @@ public class OrganizationResponseModel : ResponseModel
|
||||
BusinessCountry = organization.BusinessCountry;
|
||||
BusinessTaxNumber = organization.BusinessTaxNumber;
|
||||
BillingEmail = organization.BillingEmail;
|
||||
Plan = new PlanResponseModel(StaticStore.GetPlan(organization.PlanType));
|
||||
// Self-Host instances only require plan information that can be derived from the Organization record.
|
||||
Plan = plan != null ? new PlanResponseModel(plan) : new PlanResponseModel(organization);
|
||||
PlanType = organization.PlanType;
|
||||
Seats = organization.Seats;
|
||||
MaxAutoscaleSeats = organization.MaxAutoscaleSeats;
|
||||
@ -110,7 +114,9 @@ public class OrganizationResponseModel : ResponseModel
|
||||
|
||||
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
{
|
||||
public OrganizationSubscriptionResponseModel(Organization organization) : base(organization, "organizationSubscription")
|
||||
public OrganizationSubscriptionResponseModel(
|
||||
Organization organization,
|
||||
Plan plan) : base(organization, plan, "organizationSubscription")
|
||||
{
|
||||
Expiration = organization.ExpirationDate;
|
||||
StorageName = organization.Storage.HasValue ?
|
||||
@ -119,8 +125,11 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
Math.Round(organization.Storage.Value / 1073741824D, 2) : 0; // 1 GB
|
||||
}
|
||||
|
||||
public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription, bool hideSensitiveData)
|
||||
: this(organization)
|
||||
public OrganizationSubscriptionResponseModel(
|
||||
Organization organization,
|
||||
SubscriptionInfo subscription,
|
||||
Plan plan,
|
||||
bool hideSensitiveData) : this(organization, plan)
|
||||
{
|
||||
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
|
||||
UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
|
||||
@ -142,7 +151,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
}
|
||||
|
||||
public OrganizationSubscriptionResponseModel(Organization organization, OrganizationLicense license) :
|
||||
this(organization)
|
||||
this(organization, (Plan)null)
|
||||
{
|
||||
if (license != null)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
@ -37,7 +38,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
UsePasswordManager = organization.UsePasswordManager;
|
||||
UsersGetPremium = organization.UsersGetPremium;
|
||||
UseCustomPermissions = organization.UseCustomPermissions;
|
||||
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).ProductTier == ProductTierType.Enterprise;
|
||||
UseActivateAutofillPolicy = organization.PlanType.GetProductTier() == ProductTierType.Enterprise;
|
||||
SelfHost = organization.SelfHost;
|
||||
Seats = organization.Seats;
|
||||
MaxCollections = organization.MaxCollections;
|
||||
@ -60,7 +61,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
|
||||
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
|
||||
.UsersCanSponsor(organization);
|
||||
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
|
||||
ProductTierType = organization.PlanType.GetProductTier();
|
||||
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
|
||||
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
|
||||
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;
|
||||
|
@ -1,8 +1,8 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.AdminConsole.Models.Response;
|
||||
|
||||
@ -26,7 +26,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
||||
UseResetPassword = organization.UseResetPassword;
|
||||
UsersGetPremium = organization.UsersGetPremium;
|
||||
UseCustomPermissions = organization.UseCustomPermissions;
|
||||
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).ProductTier == ProductTierType.Enterprise;
|
||||
UseActivateAutofillPolicy = organization.PlanType.GetProductTier() == ProductTierType.Enterprise;
|
||||
SelfHost = organization.SelfHost;
|
||||
Seats = organization.Seats;
|
||||
MaxCollections = organization.MaxCollections;
|
||||
@ -44,7 +44,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
||||
ProviderId = organization.ProviderId;
|
||||
ProviderName = organization.ProviderName;
|
||||
ProviderType = organization.ProviderType;
|
||||
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
|
||||
ProductTierType = organization.PlanType.GetProductTier();
|
||||
LimitCollectionCreation = organization.LimitCollectionCreation;
|
||||
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
||||
LimitItemDeletion = organization.LimitItemDeletion;
|
||||
|
@ -4,6 +4,7 @@ using Bit.Api.Billing.Models.Requests;
|
||||
using Bit.Api.Billing.Models.Responses;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Repositories;
|
||||
@ -21,6 +22,7 @@ public class OrganizationBillingController(
|
||||
IOrganizationBillingService organizationBillingService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IPricingClient pricingClient,
|
||||
ISubscriberService subscriberService,
|
||||
IPaymentHistoryService paymentHistoryService,
|
||||
IUserService userService) : BaseBillingController
|
||||
@ -279,7 +281,7 @@ public class OrganizationBillingController(
|
||||
}
|
||||
var organizationSignup = model.ToOrganizationSignup(user);
|
||||
var sale = OrganizationSale.From(organization, organizationSignup);
|
||||
var plan = StaticStore.GetPlan(model.PlanType);
|
||||
var plan = await pricingClient.GetPlanOrThrow(model.PlanType);
|
||||
sale.Organization.PlanType = plan.Type;
|
||||
sale.Organization.Plan = plan.Name;
|
||||
sale.SubscriptionSetup.SkipTrial = true;
|
||||
|
@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
@ -45,7 +46,8 @@ public class OrganizationsController(
|
||||
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
|
||||
IReferenceEventService referenceEventService,
|
||||
ISubscriberService subscriberService,
|
||||
IOrganizationInstallationRepository organizationInstallationRepository)
|
||||
IOrganizationInstallationRepository organizationInstallationRepository,
|
||||
IPricingClient pricingClient)
|
||||
: Controller
|
||||
{
|
||||
[HttpGet("{id:guid}/subscription")]
|
||||
@ -62,26 +64,28 @@ public class OrganizationsController(
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!globalSettings.SelfHosted && organization.Gateway != null)
|
||||
{
|
||||
var subscriptionInfo = await paymentService.GetSubscriptionAsync(organization);
|
||||
if (subscriptionInfo == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var hideSensitiveData = !await currentContext.EditSubscription(id);
|
||||
|
||||
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
|
||||
}
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
var orgLicense = await licensingService.ReadOrganizationLicenseAsync(organization);
|
||||
return new OrganizationSubscriptionResponseModel(organization, orgLicense);
|
||||
}
|
||||
|
||||
return new OrganizationSubscriptionResponseModel(organization);
|
||||
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
||||
|
||||
if (string.IsNullOrEmpty(organization.GatewaySubscriptionId))
|
||||
{
|
||||
return new OrganizationSubscriptionResponseModel(organization, plan);
|
||||
}
|
||||
|
||||
var subscriptionInfo = await paymentService.GetSubscriptionAsync(organization);
|
||||
if (subscriptionInfo == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var hideSensitiveData = !await currentContext.EditSubscription(id);
|
||||
|
||||
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, plan, hideSensitiveData);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/license")]
|
||||
@ -165,7 +169,8 @@ public class OrganizationsController(
|
||||
|
||||
organization = await AdjustOrganizationSeatsForSmTrialAsync(id, organization, model);
|
||||
|
||||
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization);
|
||||
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
||||
var organizationUpdate = model.ToSecretsManagerSubscriptionUpdate(organization, plan);
|
||||
|
||||
await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(organizationUpdate);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Api.Billing.Models.Responses;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
@ -20,6 +21,7 @@ namespace Bit.Api.Billing.Controllers;
|
||||
public class ProviderBillingController(
|
||||
ICurrentContext currentContext,
|
||||
ILogger<BaseProviderController> logger,
|
||||
IPricingClient pricingClient,
|
||||
IProviderBillingService providerBillingService,
|
||||
IProviderPlanRepository providerPlanRepository,
|
||||
IProviderRepository providerRepository,
|
||||
@ -84,13 +86,25 @@ public class ProviderBillingController(
|
||||
|
||||
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
|
||||
|
||||
var configuredProviderPlans = await Task.WhenAll(providerPlans.Select(async providerPlan =>
|
||||
{
|
||||
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
|
||||
return new ConfiguredProviderPlan(
|
||||
providerPlan.Id,
|
||||
providerPlan.ProviderId,
|
||||
plan,
|
||||
providerPlan.SeatMinimum ?? 0,
|
||||
providerPlan.PurchasedSeats ?? 0,
|
||||
providerPlan.AllocatedSeats ?? 0);
|
||||
}));
|
||||
|
||||
var taxInformation = GetTaxInformation(subscription.Customer);
|
||||
|
||||
var subscriptionSuspension = await GetSubscriptionSuspensionAsync(stripeAdapter, subscription);
|
||||
|
||||
var response = ProviderSubscriptionResponse.From(
|
||||
subscription,
|
||||
providerPlans,
|
||||
configuredProviderPlans,
|
||||
taxInformation,
|
||||
subscriptionSuspension,
|
||||
provider);
|
||||
|
@ -1,9 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Utilities;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Api.Billing.Models.Responses;
|
||||
@ -25,26 +23,24 @@ public record ProviderSubscriptionResponse(
|
||||
|
||||
public static ProviderSubscriptionResponse From(
|
||||
Subscription subscription,
|
||||
ICollection<ProviderPlan> providerPlans,
|
||||
ICollection<ConfiguredProviderPlan> providerPlans,
|
||||
TaxInformation taxInformation,
|
||||
SubscriptionSuspension subscriptionSuspension,
|
||||
Provider provider)
|
||||
{
|
||||
var providerPlanResponses = providerPlans
|
||||
.Where(providerPlan => providerPlan.IsConfigured())
|
||||
.Select(ConfiguredProviderPlan.From)
|
||||
.Select(configuredProviderPlan =>
|
||||
.Select(providerPlan =>
|
||||
{
|
||||
var plan = StaticStore.GetPlan(configuredProviderPlan.PlanType);
|
||||
var cost = (configuredProviderPlan.SeatMinimum + configuredProviderPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice;
|
||||
var plan = providerPlan.Plan;
|
||||
var cost = (providerPlan.SeatMinimum + providerPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice;
|
||||
var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence;
|
||||
return new ProviderPlanResponse(
|
||||
plan.Name,
|
||||
plan.Type,
|
||||
plan.ProductTier,
|
||||
configuredProviderPlan.SeatMinimum,
|
||||
configuredProviderPlan.PurchasedSeats,
|
||||
configuredProviderPlan.AssignedSeats,
|
||||
providerPlan.SeatMinimum,
|
||||
providerPlan.PurchasedSeats,
|
||||
providerPlan.AssignedSeats,
|
||||
cost,
|
||||
cadence);
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Net;
|
||||
using Bit.Api.Billing.Public.Models;
|
||||
using Bit.Api.Models.Public.Response;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
@ -21,19 +22,22 @@ public class OrganizationController : Controller
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||
private readonly ILogger<OrganizationController> _logger;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public OrganizationController(
|
||||
IOrganizationService organizationService,
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||
ILogger<OrganizationController> logger)
|
||||
ILogger<OrganizationController> logger,
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_currentContext = currentContext;
|
||||
_organizationRepository = organizationRepository;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
_logger = logger;
|
||||
_pricingClient = pricingClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -140,7 +144,8 @@ public class OrganizationController : Controller
|
||||
return "Organization has no access to Secrets Manager.";
|
||||
}
|
||||
|
||||
var secretsManagerUpdate = model.SecretsManager.ToSecretsManagerSubscriptionUpdate(organization);
|
||||
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
|
||||
var secretsManagerUpdate = model.SecretsManager.ToSecretsManagerSubscriptionUpdate(organization, plan);
|
||||
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(secretsManagerUpdate);
|
||||
|
||||
return string.Empty;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Api.Billing.Public.Models;
|
||||
|
||||
@ -93,17 +94,17 @@ public class SecretsManagerSubscriptionUpdateModel
|
||||
set { _maxAutoScaleServiceAccounts = value < 0 ? null : value; }
|
||||
}
|
||||
|
||||
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization)
|
||||
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization, Plan plan)
|
||||
{
|
||||
var update = UpdateUpdateMaxAutoScale(organization);
|
||||
var update = UpdateUpdateMaxAutoScale(organization, plan);
|
||||
UpdateSeats(organization, update);
|
||||
UpdateServiceAccounts(organization, update);
|
||||
return update;
|
||||
}
|
||||
|
||||
private SecretsManagerSubscriptionUpdate UpdateUpdateMaxAutoScale(Organization organization)
|
||||
private SecretsManagerSubscriptionUpdate UpdateUpdateMaxAutoScale(Organization organization, Plan plan)
|
||||
{
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, false)
|
||||
var update = new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
MaxAutoscaleSmSeats = MaxAutoScaleSeats ?? organization.MaxAutoscaleSmSeats,
|
||||
MaxAutoscaleSmServiceAccounts = MaxAutoScaleServiceAccounts ?? organization.MaxAutoscaleSmServiceAccounts
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -7,13 +7,15 @@ namespace Bit.Api.Controllers;
|
||||
|
||||
[Route("plans")]
|
||||
[Authorize("Web")]
|
||||
public class PlansController : Controller
|
||||
public class PlansController(
|
||||
IPricingClient pricingClient) : Controller
|
||||
{
|
||||
[HttpGet("")]
|
||||
[AllowAnonymous]
|
||||
public ListResponseModel<PlanResponseModel> Get()
|
||||
public async Task<ListResponseModel<PlanResponseModel>> Get()
|
||||
{
|
||||
var responses = StaticStore.Plans.Select(plan => new PlanResponseModel(plan));
|
||||
var plans = await pricingClient.ListPlans();
|
||||
var responses = plans.Select(plan => new PlanResponseModel(plan));
|
||||
return new ListResponseModel<PlanResponseModel>(responses);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,8 @@ public class SelfHostedOrganizationLicensesController : Controller
|
||||
|
||||
var result = await _organizationService.SignUpAsync(license, user, model.Key,
|
||||
model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
|
||||
return new OrganizationResponseModel(result.Item1);
|
||||
|
||||
return new OrganizationResponseModel(result.Item1, null);
|
||||
}
|
||||
|
||||
[HttpPost("{id}")]
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Api.Models.Request.Organizations;
|
||||
|
||||
@ -12,9 +13,9 @@ public class SecretsManagerSubscriptionUpdateRequestModel
|
||||
public int ServiceAccountAdjustment { get; set; }
|
||||
public int? MaxAutoscaleServiceAccounts { get; set; }
|
||||
|
||||
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization)
|
||||
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization, Plan plan)
|
||||
{
|
||||
return new SecretsManagerSubscriptionUpdate(organization, false)
|
||||
return new SecretsManagerSubscriptionUpdate(organization, plan, false)
|
||||
{
|
||||
MaxAutoscaleSmSeats = MaxAutoscaleSeats,
|
||||
MaxAutoscaleSmServiceAccounts = MaxAutoscaleServiceAccounts
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
@ -44,6 +46,13 @@ public class PlanResponseModel : ResponseModel
|
||||
PasswordManager = new PasswordManagerPlanFeaturesResponseModel(plan.PasswordManager);
|
||||
}
|
||||
|
||||
public PlanResponseModel(Organization organization, string obj = "plan") : base(obj)
|
||||
{
|
||||
Type = organization.PlanType;
|
||||
ProductTier = organization.PlanType.GetProductTier();
|
||||
Name = organization.Plan;
|
||||
}
|
||||
|
||||
public PlanType Type { get; set; }
|
||||
public ProductTierType ProductTier { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.SecretsManager.Models.Request;
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -37,6 +38,7 @@ public class ServiceAccountsController : Controller
|
||||
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
|
||||
private readonly IDeleteServiceAccountsCommand _deleteServiceAccountsCommand;
|
||||
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public ServiceAccountsController(
|
||||
ICurrentContext currentContext,
|
||||
@ -52,7 +54,8 @@ public class ServiceAccountsController : Controller
|
||||
ICreateServiceAccountCommand createServiceAccountCommand,
|
||||
IUpdateServiceAccountCommand updateServiceAccountCommand,
|
||||
IDeleteServiceAccountsCommand deleteServiceAccountsCommand,
|
||||
IRevokeAccessTokensCommand revokeAccessTokensCommand)
|
||||
IRevokeAccessTokensCommand revokeAccessTokensCommand,
|
||||
IPricingClient pricingClient)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_userService = userService;
|
||||
@ -66,6 +69,7 @@ public class ServiceAccountsController : Controller
|
||||
_updateServiceAccountCommand = updateServiceAccountCommand;
|
||||
_deleteServiceAccountsCommand = deleteServiceAccountsCommand;
|
||||
_revokeAccessTokensCommand = revokeAccessTokensCommand;
|
||||
_pricingClient = pricingClient;
|
||||
_createAccessTokenCommand = createAccessTokenCommand;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
}
|
||||
@ -124,7 +128,9 @@ public class ServiceAccountsController : Controller
|
||||
if (newServiceAccountSlotsRequired > 0)
|
||||
{
|
||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
var update = new SecretsManagerSubscriptionUpdate(org, true)
|
||||
// TODO: https://bitwarden.atlassian.net/browse/PM-17002
|
||||
var plan = await _pricingClient.GetPlanOrThrow(org!.PlanType);
|
||||
var update = new SecretsManagerSubscriptionUpdate(org, plan, true)
|
||||
.AdjustServiceAccounts(newServiceAccountSlotsRequired);
|
||||
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
|
||||
}
|
||||
|
Reference in New Issue
Block a user