Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-16684] Integrate Pricing Service behind FF #5276

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

amorask-bitwarden
Copy link
Contributor

@amorask-bitwarden amorask-bitwarden commented Jan 15, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-16684

📔 Objective

The primary goal of this PR is to replace invocations of the StaticStore.Plans property and StaticStore.GetPlan method with invocations of the newly added PricingClient.

The Pricing Client

The PricingClient is a simple wrapper over an HTTP Client that sends requests to the upcoming Bitwarden Pricing Service, an API responsible for managing Bitwarden's plan information.

The key lines in the PricingClient for the purpose of this PR are:

var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService);

if (!usePricingService)
{
    return StaticStore.GetPlan(planType);
}

In effect, until the Pricing Service is actually stood up and the feature flag is enabled, we haven't really made any change to the data source we're using to get plan information. Additionally, we can maintain the same exact contract as we had with the StaticStore because ultimately, responses from the Pricing Service will be mapped to the StaticStore.Plan record type which is woven throughout the codebase. We are, however, changing the way retrieving this plan information is managed.

Plan Retrieval

There are two major changes to retrieving plans within the platform that are addressed in this PR:

  1. Our retrieval contracts have changed from PlanType -> Plan to PlanType -> Task<Plan> since, at the point the feature flag is enabled, plan retrieval will include a cache request (not implemented yet), a network request or both. This has wide-ranging effects on the platform which consistently fell into a habit of retrieving plans in areas not ideal for data retrieval such as within several layers of nested models. This PR attempts to address this as efficiently as possible with the least potential disruptions to the platform; much of this will be identified as moving plan retrieval up to the closest asynchronous point of execution and then passing the Plan down through the model chain. It's not perfect, but it's what we've got without a large refactoring of our reliance on heavy and logic-containing models.

  2. Because the Pricing Service will be an internally managed Bitwarden service, we will no longer support retrieving plans on Self-Host instances. This is, in my opinion at least, correct. Plans are our own domain construct used to facilitate the purchase of subscriptions and grant specific feature access to our Organization entities. On the cloud side, our license generation is dependent on these plans, which is fine because we can retrieve plans on cloud. On the Self-Host side, the organization is created directly from the license itself, so there should be no need to have a detailed understanding of the plan that led to the licenses generation (outside of a few pieces of data for display purposes).

Many places that were potential Self-Host usages were just retrieving a Plan for the sake of getting the ProductTier. But we don't need to do that as ProductTier can be derived directly from PlanType.

Considerations & Follow-Ups

Although I tried to remove every StaticStore invocation, there are a few spots that remain problematic.

  1. The MaxProjectsQuery can be invoked in the Self-Host path and relies on plan information. I opened https://bitwarden.atlassian.net/browse/PM-17122 to address moving the information being garnered from the plan in the query to the OrganizationLicense itself.

  2. The IsAddOnSubscriptionItem utility in the StaticStore is used very deep within nested models in a Billing response. I've opened https://bitwarden.atlassian.net/browse/PM-16844 to move this simple bool data out of our own domain and into Stripe as metadata attached to our Price entities.

  3. In the current platform, we handle an issue where the DLLs running on our users' Self-Host instances can be outdated, leading to new plan types that exist in cloud not existing on the Self-Host instance. We manage this by ensuring the PlanType attached to the OrganizationLicense exists in the StaticStore running on the user's instance. If it doesn't, we throw an exception. This PR removes that check because Self-Host instances can't invoke the Pricing Service, so this issue will ultimately reappear. I'd argue, however, that this check is unnecessary. Since the user purchased the organization subscription against Cloud, it's valid and the features assigned to the organization should be respected. It doesn't matter whether or not the new plan type exists on the Self-Host instance. The only place I can identify this being an issue is on the UpdateLicenseComponent where we check the ProductTierType of the organization. However, I think this can be managed by introducing a ProductTierType.Unkown and flipping this check to just assert the organization is Enterprise tier. (Ticket pending for this)

Throughout the course of my testing, I also uncovered at least 3 areas that are invokable from a Self-Host instance, but can attempt to update a subscription, which should not be allowed:

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

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.
@amorask-bitwarden amorask-bitwarden requested review from a team as code owners January 15, 2025 18:53
@amorask-bitwarden amorask-bitwarden changed the title [PM-16684] [PM-16684] Integrate Pricing Service Jan 15, 2025
@amorask-bitwarden amorask-bitwarden marked this pull request as draft January 15, 2025 18:53
Copy link
Contributor

github-actions bot commented Jan 15, 2025

Logo
Checkmarx One – Scan Summary & Details6e9607e2-f7be-4d5f-b241-62fa5423bd58

New Issues (17)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM Privacy_Violation /src/Core/NotificationHub/NotificationHubPushNotificationService.cs: 195
detailsMethod PushAuthRequestAsync at line 195 of /src/Core/NotificationHub/NotificationHubPushNotificationService.cs sends user information outside the a...
Attack Vector
MEDIUM Privacy_Violation /src/Core/Auth/Services/Implementations/AuthRequestService.cs: 226
detailsMethod UpdateAuthRequestAsync at line 226 of /src/Core/Auth/Services/Implementations/AuthRequestService.cs sends user information outside the appli...
Attack Vector
LOW Log_Forging /src/Admin/AdminConsole/Controllers/ProvidersController.cs: 257
detailsMethod Edit at line 257 of /src/Admin/AdminConsole/Controllers/ProvidersController.cs gets user input from element model. This element’s value flow...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/ProviderClientsController.cs: 28
detailsMethod CreateAsync at line 28 of /src/Api/Billing/Controllers/ProviderClientsController.cs gets user input from element requestBody. This element’s...
Attack Vector
LOW Log_Forging /src/Admin/AdminConsole/Controllers/ProvidersController.cs: 257
detailsMethod Edit at line 257 of /src/Admin/AdminConsole/Controllers/ProvidersController.cs gets user input from element model. This element’s value flow...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/ProviderClientsController.cs: 28
detailsMethod CreateAsync at line 28 of /src/Api/Billing/Controllers/ProviderClientsController.cs gets user input from element requestBody. This element’s...
Attack Vector
LOW Log_Forging /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 179
detailsMethod Post at line 179 of /src/Api/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. This element’s value fl...
Attack Vector
LOW Log_Forging /src/Admin/AdminConsole/Controllers/OrganizationsController.cs: 240
detailsMethod Edit at line 240 of /src/Admin/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. This element’s value ...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/InvoicesController.cs: 20
detailsMethod PreviewInvoiceAsync at line 20 of /src/Api/Billing/Controllers/InvoicesController.cs gets user input from element paymentService. This eleme...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/InvoicesController.cs: 17
detailsMethod PreviewInvoiceAsync at line 17 of /src/Api/Billing/Controllers/InvoicesController.cs gets user input from element model. This element’s valu...
Attack Vector
LOW Log_Forging /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 195
detailsMethod CreateWithoutPaymentAsync at line 195 of /src/Api/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. Th...
Attack Vector
LOW Log_Forging /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 179
detailsMethod Post at line 179 of /src/Api/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. This element’s value fl...
Attack Vector
LOW Log_Forging /src/Admin/AdminConsole/Controllers/OrganizationsController.cs: 240
detailsMethod Edit at line 240 of /src/Admin/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. This element’s value ...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/InvoicesController.cs: 17
detailsMethod PreviewInvoiceAsync at line 17 of /src/Api/Billing/Controllers/InvoicesController.cs gets user input from element model. This element’s valu...
Attack Vector
LOW Log_Forging /src/Api/Billing/Controllers/InvoicesController.cs: 20
detailsMethod PreviewInvoiceAsync at line 20 of /src/Api/Billing/Controllers/InvoicesController.cs gets user input from element paymentService. This eleme...
Attack Vector
LOW Log_Forging /src/Api/AdminConsole/Controllers/OrganizationsController.cs: 195
detailsMethod CreateWithoutPaymentAsync at line 195 of /src/Api/AdminConsole/Controllers/OrganizationsController.cs gets user input from element model. Th...
Attack Vector
LOW Missing_CSP_Header /src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.html.hbs: 9
detailsA Content Security Policy is not explicitly defined within the web-application.
Attack Vector
Fixed Issues (26)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM CSRF /src/Billing/Controllers/RecoveryController.cs: 38
MEDIUM CSRF /src/Billing/Controllers/StripeController.cs: 164
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 106
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 238
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 124
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 110
MEDIUM CSRF /src/Admin/AdminConsole/Controllers/OrganizationsController.cs: 375
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 377
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 360
MEDIUM CSRF /src/Api/AdminConsole/Controllers/OrganizationUsersController.cs: 264
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 944
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 684
MEDIUM Privacy_Violation /src/Api/Auth/Models/Request/Accounts/SetPasswordRequestModel.cs: 28
MEDIUM Privacy_Violation /src/Core/Auth/Services/Implementations/AuthRequestService.cs: 221
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/RecoveryController.cs: 38
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Api/Auth/Controllers/AccountsController.cs: 927
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Billing/Controllers/StripeController.cs: 164
LOW Log_Forging /src/Api/Auth/Controllers/AccountsController.cs: 261
LOW Log_Forging /src/Api/Auth/Controllers/AccountsController.cs: 381

Copy link

codecov bot commented Jan 15, 2025

Codecov Report

Attention: Patch coverage is 29.38856% with 358 lines in your changes missing coverage. Please review.

Project coverage is 44.25%. Comparing base (f1c94a1) to head (ceadae5).

Files with missing lines Patch % Lines
src/Core/Billing/Pricing/PlanAdapter.cs 0.00% 58 Missing ⚠️
src/Core/Billing/Pricing/PricingClient.cs 17.07% 32 Missing and 2 partials ⚠️
src/Core/Billing/Pricing/Models/PurchasableDTO.cs 0.00% 30 Missing ⚠️
...illing/Pricing/JSON/PurchasableDTOJsonConverter.cs 0.00% 27 Missing ⚠️
...ing/Pricing/JSON/FreeOrScalableDTOJsonConverter.cs 0.00% 21 Missing ⚠️
...ices/Implementations/SubscriptionUpdatedHandler.cs 0.00% 20 Missing ⚠️
src/Core/Billing/Pricing/Models/PlanDTO.cs 0.00% 16 Missing ⚠️
...Api/Billing/Controllers/OrganizationsController.cs 12.50% 14 Missing ⚠️
...dminConsole/Controllers/OrganizationsController.cs 20.00% 12 Missing ⚠️
...ervices/Implementations/PaymentSucceededHandler.cs 0.00% 11 Missing ⚠️
... and 31 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5276      +/-   ##
==========================================
- Coverage   44.32%   44.25%   -0.07%     
==========================================
  Files        1483     1490       +7     
  Lines       68416    68604     +188     
  Branches     6173     6173              
==========================================
+ Hits        30325    30362      +37     
- Misses      36782    36927     +145     
- Partials     1309     1315       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@amorask-bitwarden amorask-bitwarden changed the title [PM-16684] Integrate Pricing Service [PM-16684] Integrate Pricing Service behind FF Jan 15, 2025
@amorask-bitwarden amorask-bitwarden marked this pull request as ready for review January 16, 2025 16:46
@withinfocus
Copy link
Contributor

The tweaks make sense to me -- on to true team review though.

Copy link
Contributor

@r-tome r-tome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Just left a suggestion

message: $"Request to the Pricing Service failed with status {response.StatusCode}");
}

private static async Task<T> DeserializeAsync<T>(HttpContent content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be replaced by ReadFromJsonAsync in System.Net.Http.Json

Copy link
Contributor Author

@amorask-bitwarden amorask-bitwarden Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call! Replaced in 8d60635.

return new PlanAdapter(response);
if (response.IsSuccessStatusCode)
{
var plan = await DeserializeAsync<PlanDTO>(response.Content);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var plan = await DeserializeAsync<PlanDTO>(response.Content);
var plan = await response.Content.ReadFromJsonAsync<PlanDTO>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 8d60635

r-tome
r-tome previously approved these changes Jan 24, 2025
Copy link
Contributor

@r-tome r-tome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Thanks for implementing my suggestion

Copy link
Contributor

@cturnbull-bitwarden cturnbull-bitwarden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent writeup, thank you sir

@bitwarden-devops-bot
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants