mirror of
https://github.com/bitwarden/server
synced 2025-12-11 05:43:35 +00:00
* Upgrade Stripe.net to v48.4.0 * Update PreviewTaxAmountCommand * Remove unused UpcomingInvoiceOptionExtensions * Added SubscriptionExtensions with GetCurrentPeriodEnd * Update PremiumUserBillingService * Update OrganizationBillingService * Update GetOrganizationWarningsQuery * Update BillingHistoryInfo * Update SubscriptionInfo * Remove unused Sql Billing folder * Update StripeAdapter * Update StripePaymentService * Update InvoiceCreatedHandler * Update PaymentFailedHandler * Update PaymentSucceededHandler * Update ProviderEventService * Update StripeEventUtilityService * Update SubscriptionDeletedHandler * Update SubscriptionUpdatedHandler * Update UpcomingInvoiceHandler * Update ProviderSubscriptionResponse * Remove unused Stripe Subscriptions Admin Tool * Update RemoveOrganizationFromProviderCommand * Update ProviderBillingService * Update RemoveOrganizatinoFromProviderCommandTests * Update PreviewTaxAmountCommandTests * Update GetCloudOrganizationLicenseQueryTests * Update GetOrganizationWarningsQueryTests * Update StripePaymentServiceTests * Update ProviderBillingControllerTests * Update ProviderEventServiceTests * Update SubscriptionDeletedHandlerTests * Update SubscriptionUpdatedHandlerTests * Resolve Billing test failures I completely removed tests for the StripeEventService as they were using a system I setup a while back that read JSON files of the Stripe event structure. I did not anticipate how frequently these structures would change with each API version and the cost of trying to update these specific JSON files to test a very static data retrieval service far outweigh the benefit. * Resolve Core test failures * Run dotnet format * Remove unused provider migration * Fixed failing tests * Run dotnet format * Replace the old webhook secret key with new one (#6223) * Fix compilation failures in additions * Run dotnet format * Bump Stripe API version * Fix recent addition: CreatePremiumCloudHostedSubscriptionCommand * Fix new code in main according to Stripe update * Fix InvoiceExtensions * Bump SDK version to match API Version * Fix provider invoice generation validation * More QA fixes * Fix tests * QA defect resolutions * QA defect resolutions * Run dotnet format * Fix tests --------- Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
177 lines
7.9 KiB
C#
177 lines
7.9 KiB
C#
using Bit.Core.Billing.Models.Business;
|
|
using Bit.Core.Billing.Services;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Models.Business;
|
|
using NSubstitute;
|
|
using Stripe;
|
|
using Xunit;
|
|
|
|
namespace Bit.Core.Test.Billing.Models.Business;
|
|
|
|
public class UserLicenseTests
|
|
{
|
|
/// <summary>
|
|
/// Known good GetDataBytes output for hash data (forHash: true) for UserLicense version 1.
|
|
/// This value was verified to be correct on initial implementation and serves as a regression baseline.
|
|
/// NOTE: License versions are now frozen. Use the JWT Token property to add new claims instead of incrementing the version.
|
|
/// </summary>
|
|
private const string _knownGoodUserLicenseHashData = "license:user|Email:test@example.com|Expires:1736208000|Id:12300000-0000-0000-0000-000000000789|LicenseKey:myUserLicenseKey|MaxStorageGb:10|Name:Test User|Premium:true|Trial:false|Version:1";
|
|
|
|
/// <summary>
|
|
/// Known good GetDataBytes output for signature data (forHash: false) for UserLicense version 1.
|
|
/// This value was verified to be correct on initial implementation and serves as a regression baseline.
|
|
/// NOTE: License versions are now frozen. Use the JWT Token property to add new claims instead of incrementing the version.
|
|
/// </summary>
|
|
private const string _knownGoodUserLicenseSignatureData = "license:user|Email:test@example.com|Expires:1736208000|Hash:oZEopNmWvWQNE3Lnsh/LP2OPo6+IHxjTcpdIse/viQk=|Id:12300000-0000-0000-0000-000000000789|Issued:1758888041|LicenseKey:myUserLicenseKey|MaxStorageGb:10|Name:Test User|Premium:true|Refresh:1735603200|Trial:false|Version:1";
|
|
|
|
/// <summary>
|
|
/// Regression test that verifies GetDataBytes output for hash data (forHash: true) remains stable for UserLicense version 1.
|
|
/// This protects against accidental changes to the data format that would break backward compatibility.
|
|
/// If this test fails, it means the hash data format has changed and existing licenses may no longer validate.
|
|
/// </summary>
|
|
[Fact]
|
|
public void UserLicense_GetDataBytes_HashData_Version1()
|
|
{
|
|
var license = CreateDeterministicUserLicense();
|
|
var actualHashData = System.Text.Encoding.UTF8.GetString(license.GetDataBytes(forHash: true));
|
|
Assert.Equal(_knownGoodUserLicenseHashData, actualHashData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Regression test that verifies GetDataBytes output for signature data (forHash: false) remains stable for UserLicense version 1.
|
|
/// This protects against accidental changes to the data format that would break backward compatibility.
|
|
/// If this test fails, it means the signature data format has changed and existing licenses may no longer validate.
|
|
/// </summary>
|
|
[Fact]
|
|
public void UserLicense_GetDataBytes_SignatureData_Version1()
|
|
{
|
|
var license = CreateDeterministicUserLicense();
|
|
var actualSignatureData = System.Text.Encoding.UTF8.GetString(license.GetDataBytes(forHash: false));
|
|
Assert.Equal(_knownGoodUserLicenseSignatureData, actualSignatureData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates that the UserLicense version remains frozen at version 1.
|
|
/// License versions should no longer be incremented. Use the JWT Token property to add new claims instead.
|
|
/// If this test fails, it means someone attempted to add version 2 support, which is no longer allowed.
|
|
/// </summary>
|
|
[Fact]
|
|
public void UserLicense_CurrentVersion_ShouldRemainFrozen()
|
|
{
|
|
const int expectedMaxVersion = 1;
|
|
|
|
var user = CreateDeterministicUser();
|
|
var subscriptionInfo = CreateDeterministicSubscriptionInfo();
|
|
var mockLicensingService = CreateMockLicensingService();
|
|
|
|
// Verify that version 2 is NOT supported (should throw NotSupportedException)
|
|
var exception = Assert.Throws<NotSupportedException>(() =>
|
|
new UserLicense(user, subscriptionInfo, mockLicensingService, version: 2));
|
|
|
|
// If the exception message changes or we don't get an exception, fail with helpful guidance
|
|
if (exception == null)
|
|
{
|
|
var errorMessage = $@"
|
|
ERROR: UserLicense now supports version 2 or higher
|
|
|
|
License versions are now frozen and should not be incremented.
|
|
|
|
Instead of incrementing the version:
|
|
- Use the JWT Token property to add new claims
|
|
- Add your new capabilities as claims in the Token
|
|
- This allows for more flexible licensing without breaking backward compatibility
|
|
|
|
If you believe you need to change the version for a valid reason, please discuss with the team first.
|
|
";
|
|
Assert.Fail(errorMessage);
|
|
}
|
|
|
|
// Verify we still support version 1
|
|
var license = new UserLicense(user, subscriptionInfo, mockLicensingService, version: expectedMaxVersion);
|
|
Assert.NotNull(license);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a deterministic UserLicense for testing hash values.
|
|
/// All property values are fixed to ensure reproducible hashes.
|
|
/// </summary>
|
|
private static UserLicense CreateDeterministicUserLicense()
|
|
{
|
|
var user = CreateDeterministicUser();
|
|
var subscriptionInfo = CreateDeterministicSubscriptionInfo();
|
|
var mockLicensingService = CreateMockLicensingService();
|
|
|
|
var license = new UserLicense(user, subscriptionInfo, mockLicensingService, version: 1);
|
|
|
|
// Override timestamps to deterministic values (constructor sets them to DateTime.UtcNow)
|
|
license.Issued = new DateTime(2025, 9, 26, 12, 0, 41, DateTimeKind.Utc); // Corresponds to 1759502041 Unix timestamp
|
|
license.Refresh = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc); // Corresponds to 1735603200 Unix timestamp
|
|
|
|
// Recalculate hash with the deterministic Issued/Refresh values
|
|
license.Hash = Convert.ToBase64String(license.ComputeHash());
|
|
license.Signature = Convert.ToBase64String(mockLicensingService.SignLicense(license));
|
|
|
|
return license;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a User with deterministic property values for reproducible testing.
|
|
/// </summary>
|
|
private static User CreateDeterministicUser()
|
|
{
|
|
return new User
|
|
{
|
|
Id = new Guid("12300000-0000-0000-0000-000000000789"),
|
|
Name = "Test User",
|
|
Email = "test@example.com",
|
|
LicenseKey = "myUserLicenseKey",
|
|
Premium = true,
|
|
MaxStorageGb = 10,
|
|
PremiumExpirationDate = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a SubscriptionInfo with deterministic dates for reproducible testing.
|
|
/// </summary>
|
|
private static SubscriptionInfo CreateDeterministicSubscriptionInfo()
|
|
{
|
|
var stripeSubscription = new Subscription
|
|
{
|
|
Status = "active",
|
|
TrialStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
TrialEnd = new DateTime(2024, 2, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = [
|
|
new SubscriptionItem
|
|
{
|
|
CurrentPeriodStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
CurrentPeriodEnd = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
return new SubscriptionInfo
|
|
{
|
|
UpcomingInvoice = new SubscriptionInfo.BillingUpcomingInvoice
|
|
{
|
|
Date = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc)
|
|
},
|
|
Subscription = new SubscriptionInfo.BillingSubscription(stripeSubscription)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mock ILicensingService that returns a deterministic signature.
|
|
/// </summary>
|
|
private static ILicensingService CreateMockLicensingService()
|
|
{
|
|
var mockService = Substitute.For<ILicensingService>();
|
|
mockService.SignLicense(Arg.Any<ILicense>())
|
|
.Returns([0x00, 0x01, 0x02, 0x03]); // Dummy signature for hash testing
|
|
return mockService;
|
|
}
|
|
}
|