1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-06 10:32:49 -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:
Alex Morask
2025-02-27 07:55:46 -05:00
committed by GitHub
parent bd66f06bd9
commit a2e665cb96
78 changed files with 1178 additions and 712 deletions

View File

@ -2,6 +2,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Pricing;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -24,6 +25,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
private readonly ICollectionRepository _collectionRepository;
private readonly IGroupRepository _groupRepository;
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
private readonly IPricingClient _pricingClient;
public UpdateOrganizationUserCommand(
IEventService eventService,
@ -34,7 +36,8 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
ICollectionRepository collectionRepository,
IGroupRepository groupRepository,
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery)
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
IPricingClient pricingClient)
{
_eventService = eventService;
_organizationService = organizationService;
@ -45,6 +48,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
_collectionRepository = collectionRepository;
_groupRepository = groupRepository;
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
_pricingClient = pricingClient;
}
/// <summary>
@ -128,8 +132,10 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organizationUser.OrganizationId, 1);
if (additionalSmSeatsRequired > 0)
{
var update = new SecretsManagerSubscriptionUpdate(organization, true)
.AdjustSeats(additionalSmSeatsRequired);
// TODO: https://bitwarden.atlassian.net/browse/PM-17012
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
var update = new SecretsManagerSubscriptionUpdate(organization, plan, true)
.AdjustSeats(additionalSmSeatsRequired);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update);
}
}

View File

@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models.Sales;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Services;
using Bit.Core.Context;
using Bit.Core.Entities;
@ -45,11 +46,12 @@ public class CloudOrganizationSignUpCommand(
IPushRegistrationService pushRegistrationService,
IPushNotificationService pushNotificationService,
ICollectionRepository collectionRepository,
IDeviceRepository deviceRepository) : ICloudOrganizationSignUpCommand
IDeviceRepository deviceRepository,
IPricingClient pricingClient) : ICloudOrganizationSignUpCommand
{
public async Task<SignUpOrganizationResponse> SignUpOrganizationAsync(OrganizationSignup signup)
{
var plan = StaticStore.GetPlan(signup.Plan);
var plan = await pricingClient.GetPlanOrThrow(signup.Plan);
ValidatePasswordManagerPlan(plan, signup);

View File

@ -16,6 +16,7 @@ using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Billing.Constants;
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.Entities;
@ -74,6 +75,7 @@ public class OrganizationService : IOrganizationService
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IOrganizationBillingService _organizationBillingService;
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
private readonly IPricingClient _pricingClient;
public OrganizationService(
IOrganizationRepository organizationRepository,
@ -108,7 +110,8 @@ public class OrganizationService : IOrganizationService
IFeatureService featureService,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IOrganizationBillingService organizationBillingService,
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery)
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
IPricingClient pricingClient)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -143,6 +146,7 @@ public class OrganizationService : IOrganizationService
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_organizationBillingService = organizationBillingService;
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
_pricingClient = pricingClient;
}
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
@ -210,11 +214,7 @@ public class OrganizationService : IOrganizationService
throw new NotFoundException();
}
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
if (!plan.PasswordManager.HasAdditionalStorageOption)
{
@ -268,7 +268,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"Cannot set max seat autoscaling below current seat count.");
}
var plan = StaticStore.GetPlan(organization.PlanType);
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
@ -320,11 +320,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("No subscription found.");
}
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
if (!plan.PasswordManager.HasAdditionalSeatsOption)
{
@ -442,7 +438,7 @@ public class OrganizationService : IOrganizationService
public async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup)
{
var plan = StaticStore.GetPlan(signup.Plan);
var plan = await _pricingClient.GetPlanOrThrow(signup.Plan);
ValidatePlan(plan, signup.AdditionalSeats, "Password Manager");
@ -530,17 +526,6 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException(exception);
}
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == license.PlanType);
if (plan is null)
{
throw new BadRequestException($"Server must be updated to support {license.Plan}.");
}
if (license.PlanType != PlanType.Custom && plan.Disabled)
{
throw new BadRequestException($"Plan {plan.Name} is disabled.");
}
var enabledOrgs = await _organizationRepository.GetManyByEnabledAsync();
if (enabledOrgs.Any(o => string.Equals(o.LicenseKey, license.LicenseKey)))
{
@ -882,7 +867,8 @@ public class OrganizationService : IOrganizationService
var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount);
if (additionalSmSeatsRequired > 0)
{
smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, true)
var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType);
smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, plan, true)
.AdjustSeats(additionalSmSeatsRequired);
}
@ -1008,7 +994,8 @@ public class OrganizationService : IOrganizationService
if (initialSmSeatCount.HasValue && currentOrganization.SmSeats.HasValue &&
currentOrganization.SmSeats.Value != initialSmSeatCount.Value)
{
var smSubscriptionUpdateRevert = new SecretsManagerSubscriptionUpdate(currentOrganization, false)
var plan = await _pricingClient.GetPlanOrThrow(currentOrganization.PlanType);
var smSubscriptionUpdateRevert = new SecretsManagerSubscriptionUpdate(currentOrganization, plan, false)
{
SmSeats = initialSmSeatCount.Value
};
@ -2237,13 +2224,6 @@ public class OrganizationService : IOrganizationService
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
{
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan!.Disabled)
{
throw new BadRequestException("Plan not found.");
}
organization.Id = CoreHelpers.GenerateComb();
organization.Enabled = false;
organization.Status = OrganizationStatusType.Pending;