mirror of
https://github.com/bitwarden/server
synced 2026-02-19 10:53:34 +00:00
chore: [PM-29055] remove pm-25379-use-new-organization-metadata-structure feature flag (#6966)
Remove the fully-released feature flag and clean up the old code path: - Remove flag constant from FeatureFlagKeys - Remove [RequireFeature] gate from VNext billing controllers - Remove old GetMetadataAsync endpoint from OrganizationBillingController - Remove GetMetadata from IOrganizationBillingService and implementation - Remove IsOnSecretsManagerStandalone private helper - Remove associated tests
This commit is contained in:
@@ -21,25 +21,6 @@ public class OrganizationBillingController(
|
||||
IStripePaymentService paymentService,
|
||||
IPaymentHistoryService paymentHistoryService) : BaseBillingController
|
||||
{
|
||||
// TODO: Remove when pm-25379-use-new-organization-metadata-structure is removed.
|
||||
[HttpGet("metadata")]
|
||||
public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId)
|
||||
{
|
||||
if (!await currentContext.OrganizationUser(organizationId))
|
||||
{
|
||||
return Error.Unauthorized();
|
||||
}
|
||||
|
||||
var metadata = await organizationBillingService.GetMetadata(organizationId);
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
return Error.NotFound();
|
||||
}
|
||||
|
||||
return TypedResults.Ok(metadata);
|
||||
}
|
||||
|
||||
// TODO: Migrate to Query / OrganizationBillingVNextController
|
||||
[HttpGet("history")]
|
||||
public async Task<IResult> GetHistoryAsync([FromRoute] Guid organizationId)
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.Api.Billing.Attributes;
|
||||
using Bit.Api.Billing.Models.Requests.Payment;
|
||||
using Bit.Api.Billing.Models.Requests.Subscriptions;
|
||||
using Bit.Api.Billing.Models.Requirements;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Commands;
|
||||
using Bit.Core.Billing.Organizations.Queries;
|
||||
@@ -117,7 +116,6 @@ public class OrganizationBillingVNextController(
|
||||
|
||||
[Authorize<MemberOrProviderRequirement>]
|
||||
[HttpGet("metadata")]
|
||||
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
|
||||
[InjectOrganization]
|
||||
public async Task<IResult> GetMetadataAsync(
|
||||
[BindNever] Organization organization)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Bit.Api.AdminConsole.Authorization;
|
||||
using Bit.Api.AdminConsole.Authorization.Requirements;
|
||||
using Bit.Api.Billing.Attributes;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Organizations.Queries;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -19,7 +18,6 @@ public class SelfHostedOrganizationBillingVNextController(
|
||||
{
|
||||
[Authorize<MemberOrProviderRequirement>]
|
||||
[HttpGet("metadata")]
|
||||
[RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)]
|
||||
[InjectOrganization]
|
||||
public async Task<IResult> GetMetadataAsync([BindNever] Organization organization)
|
||||
{
|
||||
|
||||
@@ -26,13 +26,6 @@ public interface IOrganizationBillingService
|
||||
/// </example>
|
||||
Task Finalize(OrganizationSale sale);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve metadata about the organization represented bsy the provided <paramref name="organizationId"/>.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The ID of the organization to retrieve metadata for.</param>
|
||||
/// <returns>An <see cref="OrganizationMetadata"/> record.</returns>
|
||||
Task<OrganizationMetadata?> GetMetadata(Guid organizationId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the provided <paramref name="organization"/>'s payment source and tax information.
|
||||
/// If the <paramref name="organization"/> does not have a Stripe <see cref="Stripe.Customer"/>, this method will create one using the provided
|
||||
|
||||
@@ -54,52 +54,6 @@ public class OrganizationBillingService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<OrganizationMetadata?> GetMetadata(Guid organizationId)
|
||||
{
|
||||
var organization = await organizationRepository.GetByIdAsync(organizationId);
|
||||
|
||||
if (organization == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
return OrganizationMetadata.Default;
|
||||
}
|
||||
|
||||
var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
||||
{
|
||||
return OrganizationMetadata.Default with
|
||||
{
|
||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
||||
};
|
||||
}
|
||||
|
||||
var customer = await subscriberService.GetCustomer(organization);
|
||||
|
||||
var subscription = await subscriberService.GetSubscription(organization, new SubscriptionGetOptions
|
||||
{
|
||||
Expand = ["discounts.coupon.applies_to"]
|
||||
});
|
||||
|
||||
if (customer == null || subscription == null)
|
||||
{
|
||||
return OrganizationMetadata.Default with
|
||||
{
|
||||
OrganizationOccupiedSeats = orgOccupiedSeats.Total
|
||||
};
|
||||
}
|
||||
|
||||
var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription);
|
||||
|
||||
return new OrganizationMetadata(
|
||||
isOnSecretsManagerStandalone,
|
||||
orgOccupiedSeats.Total);
|
||||
}
|
||||
|
||||
public async Task UpdatePaymentMethod(
|
||||
Organization organization,
|
||||
TokenizedPaymentSource tokenizedPaymentSource,
|
||||
@@ -565,38 +519,6 @@ public class OrganizationBillingService(
|
||||
return customer;
|
||||
}
|
||||
|
||||
private async Task<bool> IsOnSecretsManagerStandalone(
|
||||
Organization organization,
|
||||
Customer? customer,
|
||||
Subscription? subscription)
|
||||
{
|
||||
if (customer == null || subscription == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var plan = await pricingClient.GetPlanOrThrow(organization.PlanType);
|
||||
|
||||
if (!plan.SupportsSecretsManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var coupon = subscription.Discounts?.FirstOrDefault(discount =>
|
||||
discount.Coupon?.Id == StripeConstants.CouponIDs.SecretsManagerStandalone)?.Coupon;
|
||||
|
||||
if (coupon == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var subscriptionProductIds = subscription.Items.Data.Select(item => item.Plan.ProductId);
|
||||
|
||||
var couponAppliesTo = coupon.AppliesTo?.Products;
|
||||
|
||||
return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any();
|
||||
}
|
||||
|
||||
private async Task UpdateMissingPaymentMethodBehaviourAsync(Organization organization)
|
||||
{
|
||||
var subscription = await subscriberService.GetSubscriptionOrThrow(organization);
|
||||
|
||||
@@ -182,7 +182,6 @@ public static class FeatureFlagKeys
|
||||
|
||||
/* Billing Team */
|
||||
public const string TrialPayment = "PM-8163-trial-payment";
|
||||
public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure";
|
||||
public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button";
|
||||
public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog";
|
||||
public const string PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Bit.Api.Billing.Controllers;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Repositories;
|
||||
@@ -20,50 +18,6 @@ namespace Bit.Api.Test.Billing.Controllers;
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationBillingControllerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetMetadataAsync_Unauthorized_ReturnsUnauthorized(
|
||||
Guid organizationId,
|
||||
SutProvider<OrganizationBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().AccessMembersTab(organizationId).Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
||||
|
||||
AssertUnauthorized(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetMetadataAsync_MetadataNull_NotFound(
|
||||
Guid organizationId,
|
||||
SutProvider<OrganizationBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId).Returns((OrganizationMetadata)null);
|
||||
|
||||
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
||||
|
||||
AssertNotFound(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetMetadataAsync_OK(
|
||||
Guid organizationId,
|
||||
SutProvider<OrganizationBillingController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId)
|
||||
.Returns(new OrganizationMetadata(true, 10));
|
||||
|
||||
var result = await sutProvider.Sut.GetMetadataAsync(organizationId);
|
||||
|
||||
Assert.IsType<Ok<OrganizationMetadata>>(result);
|
||||
|
||||
var response = ((Ok<OrganizationMetadata>)result).Value;
|
||||
|
||||
Assert.True(response.IsOnSecretsManagerStandalone);
|
||||
Assert.Equal(10, response.OrganizationOccupiedSeats);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetHistoryAsync_Unauthorized_ReturnsUnauthorized(
|
||||
Guid organizationId,
|
||||
|
||||
@@ -7,7 +7,6 @@ using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@@ -21,110 +20,6 @@ namespace Bit.Core.Test.Billing.Services;
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationBillingServiceTests
|
||||
{
|
||||
#region GetMetadata
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetMetadata_Succeeds(
|
||||
Guid organizationId,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
var organizationSeatCount = new OrganizationSeatCounts { Users = 1, Sponsored = 0 };
|
||||
var customer = new Customer();
|
||||
|
||||
subscriberService
|
||||
.GetCustomer(organization)
|
||||
.Returns(customer);
|
||||
|
||||
subscriberService.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
|
||||
options.Expand.Contains("discounts.coupon.applies_to"))).Returns(new Subscription
|
||||
{
|
||||
Discounts =
|
||||
[
|
||||
new Discount
|
||||
{
|
||||
Coupon = new Coupon
|
||||
{
|
||||
Id = StripeConstants.CouponIDs.SecretsManagerStandalone,
|
||||
AppliesTo = new CouponAppliesTo
|
||||
{
|
||||
Products = ["product_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
Items = new StripeList<SubscriptionItem>
|
||||
{
|
||||
Data =
|
||||
[
|
||||
new SubscriptionItem
|
||||
{
|
||||
Plan = new Plan
|
||||
{
|
||||
ProductId = "product_id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
||||
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });
|
||||
|
||||
var metadata = await sutProvider.Sut.GetMetadata(organizationId);
|
||||
|
||||
Assert.True(metadata!.IsOnSecretsManagerStandalone);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetMetadata - Null Customer or Subscription
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetMetadata_WhenCustomerOrSubscriptionIsNull_ReturnsDefaultMetadata(
|
||||
Guid organizationId,
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().ListPlans().Returns(MockPlans.Plans.ToList());
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(organization.PlanType)
|
||||
.Returns(MockPlans.Get(organization.PlanType));
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id)
|
||||
.Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 });
|
||||
|
||||
var subscriberService = sutProvider.GetDependency<ISubscriberService>();
|
||||
|
||||
// Set up subscriber service to return null for customer
|
||||
subscriberService
|
||||
.GetCustomer(organization)
|
||||
.Returns((Customer)null);
|
||||
|
||||
// Set up subscriber service to return null for subscription
|
||||
subscriberService.GetSubscription(organization, Arg.Is<SubscriptionGetOptions>(options =>
|
||||
options.Expand.Contains("discounts.coupon.applies_to"))).Returns((Subscription)null);
|
||||
|
||||
var metadata = await sutProvider.Sut.GetMetadata(organizationId);
|
||||
|
||||
Assert.NotNull(metadata);
|
||||
Assert.False(metadata!.IsOnSecretsManagerStandalone);
|
||||
Assert.Equal(1, metadata.OrganizationOccupiedSeats);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Finalize - Trial Settings
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
Reference in New Issue
Block a user