From a2109175b0f304372b7f600f83b8076f0883f0e8 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:02:21 -0400 Subject: [PATCH] Add self-host eligibility to organization metadata (#4867) --- .../Responses/OrganizationMetadataResponse.cs | 5 ++- .../Billing/Models/OrganizationMetadata.cs | 4 +- .../OrganizationBillingService.cs | 40 +++++++++++++++---- .../OrganizationBillingControllerTests.cs | 3 +- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs index 624eab1fece8..b5f9ab2f5910 100644 --- a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs +++ b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs @@ -3,8 +3,11 @@ namespace Bit.Api.Billing.Models.Responses; public record OrganizationMetadataResponse( + bool IsEligibleForSelfHost, bool IsOnSecretsManagerStandalone) { public static OrganizationMetadataResponse From(OrganizationMetadata metadata) - => new(metadata.IsOnSecretsManagerStandalone); + => new( + metadata.IsEligibleForSelfHost, + metadata.IsOnSecretsManagerStandalone); } diff --git a/src/Core/Billing/Models/OrganizationMetadata.cs b/src/Core/Billing/Models/OrganizationMetadata.cs index decc35ffd8c5..136964d7c10a 100644 --- a/src/Core/Billing/Models/OrganizationMetadata.cs +++ b/src/Core/Billing/Models/OrganizationMetadata.cs @@ -1,8 +1,10 @@ namespace Bit.Core.Billing.Models; public record OrganizationMetadata( + bool IsEligibleForSelfHost, bool IsOnSecretsManagerStandalone) { public static OrganizationMetadata Default() => new( - IsOnSecretsManagerStandalone: default); + IsEligibleForSelfHost: false, + IsOnSecretsManagerStandalone: false); } diff --git a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs index 3c5938cab831..7db886203296 100644 --- a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs +++ b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Caches; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Models; @@ -26,6 +27,7 @@ public class OrganizationBillingService( IGlobalSettings globalSettings, ILogger logger, IOrganizationRepository organizationRepository, + IProviderRepository providerRepository, ISetupIntentCache setupIntentCache, IStripeAdapter stripeAdapter, ISubscriberService subscriberService) : IOrganizationBillingService @@ -69,14 +71,11 @@ public async Task Finalize(OrganizationSale sale) var subscription = await subscriberService.GetSubscription(organization); - if (customer == null || subscription == null) - { - return OrganizationMetadata.Default(); - } + var isEligibleForSelfHost = await IsEligibleForSelfHost(organization, subscription); var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription); - return new OrganizationMetadata(isOnSecretsManagerStandalone); + return new OrganizationMetadata(isEligibleForSelfHost, isOnSecretsManagerStandalone); } public async Task UpdatePaymentMethod( @@ -340,11 +339,38 @@ private async Task CreateSubscriptionAsync( return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); } + private async Task IsEligibleForSelfHost( + Organization organization, + Subscription? organizationSubscription) + { + if (organization.Status != OrganizationStatusType.Managed) + { + return organization.Plan.Contains("Families") || + organization.Plan.Contains("Enterprise") && IsActive(organizationSubscription); + } + + var provider = await providerRepository.GetByOrganizationIdAsync(organization.Id); + + var providerSubscription = await subscriberService.GetSubscriptionOrThrow(provider); + + return organization.Plan.Contains("Enterprise") && IsActive(providerSubscription); + + bool IsActive(Subscription? subscription) => subscription?.Status is + StripeConstants.SubscriptionStatus.Active or + StripeConstants.SubscriptionStatus.Trialing or + StripeConstants.SubscriptionStatus.PastDue; + } + private static bool IsOnSecretsManagerStandalone( Organization organization, - Customer customer, - Subscription subscription) + Customer? customer, + Subscription? subscription) { + if (customer == null || subscription == null) + { + return false; + } + var plan = StaticStore.GetPlan(organization.PlanType); if (!plan.SupportsSecretsManager) diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index 70ca599400e1..b46fd307e96b 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -52,7 +52,7 @@ public async Task GetMetadataAsync_OK( { sutProvider.GetDependency().AccessMembersTab(organizationId).Returns(true); sutProvider.GetDependency().GetMetadata(organizationId) - .Returns(new OrganizationMetadata(true)); + .Returns(new OrganizationMetadata(true, true)); var result = await sutProvider.Sut.GetMetadataAsync(organizationId); @@ -60,6 +60,7 @@ public async Task GetMetadataAsync_OK( var organizationMetadataResponse = ((Ok)result).Value; + Assert.True(organizationMetadataResponse.IsEligibleForSelfHost); Assert.True(organizationMetadataResponse.IsOnSecretsManagerStandalone); }