mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-25533][BEEEP] Refactor license date calculations into extensions (#6295)
* Refactor license date calculations into extensions * `dotnet format` * Handling case when expirationWithoutGracePeriod is null * Removed extra UseAdminSponsoredFamilies claim
This commit is contained in:
@@ -9,75 +9,108 @@ namespace Bit.Core.Billing.Licenses.Extensions;
|
||||
|
||||
public static class LicenseExtensions
|
||||
{
|
||||
public static DateTime CalculateFreshExpirationDate(this Organization org, SubscriptionInfo subscriptionInfo)
|
||||
public static DateTime CalculateFreshExpirationDate(this Organization org, SubscriptionInfo subscriptionInfo, DateTime issued)
|
||||
{
|
||||
|
||||
if (subscriptionInfo?.Subscription == null)
|
||||
{
|
||||
if (org.ExpirationDate.HasValue)
|
||||
{
|
||||
return org.ExpirationDate.Value;
|
||||
}
|
||||
|
||||
return DateTime.UtcNow.AddDays(7);
|
||||
// Subscription isn't setup yet, so fallback to the organization's expiration date
|
||||
// If there isn't an expiration date on the org, treat it as a free trial
|
||||
return org.ExpirationDate ?? issued.AddDays(7);
|
||||
}
|
||||
|
||||
var subscription = subscriptionInfo.Subscription;
|
||||
|
||||
if (subscription.TrialEndDate > DateTime.UtcNow)
|
||||
{
|
||||
// Still trialing, use trial's end date
|
||||
return subscription.TrialEndDate.Value;
|
||||
}
|
||||
|
||||
if (org.ExpirationDate.HasValue && org.ExpirationDate.Value < DateTime.UtcNow)
|
||||
if (org.ExpirationDate < DateTime.UtcNow)
|
||||
{
|
||||
// Organization is expired
|
||||
return org.ExpirationDate.Value;
|
||||
}
|
||||
|
||||
if (subscription.PeriodEndDate.HasValue && subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
if (subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
{
|
||||
// Annual subscription - include grace period to give the administrators time to upload a new license
|
||||
return subscription.PeriodEndDate
|
||||
.Value
|
||||
.AddDays(Bit.Core.Constants.OrganizationSelfHostSubscriptionGracePeriodDays);
|
||||
!.Value
|
||||
.AddDays(Core.Constants.OrganizationSelfHostSubscriptionGracePeriodDays);
|
||||
}
|
||||
|
||||
return org.ExpirationDate?.AddMonths(11) ?? DateTime.UtcNow.AddYears(1);
|
||||
// Monthly subscription - giving an annual expiration to not burnden admins to upload fresh licenses each month
|
||||
return org.ExpirationDate?.AddMonths(11) ?? issued.AddYears(1);
|
||||
}
|
||||
|
||||
public static DateTime CalculateFreshRefreshDate(this Organization org, SubscriptionInfo subscriptionInfo, DateTime expirationDate)
|
||||
public static DateTime CalculateFreshRefreshDate(this Organization org, SubscriptionInfo subscriptionInfo, DateTime issued)
|
||||
{
|
||||
if (subscriptionInfo?.Subscription == null ||
|
||||
subscriptionInfo.Subscription.TrialEndDate > DateTime.UtcNow ||
|
||||
org.ExpirationDate < DateTime.UtcNow)
|
||||
{
|
||||
return expirationDate;
|
||||
}
|
||||
|
||||
return subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180) ||
|
||||
DateTime.UtcNow - expirationDate > TimeSpan.FromDays(30)
|
||||
? DateTime.UtcNow.AddDays(30)
|
||||
: expirationDate;
|
||||
}
|
||||
|
||||
public static DateTime CalculateFreshExpirationDateWithoutGracePeriod(this Organization org, SubscriptionInfo subscriptionInfo, DateTime expirationDate)
|
||||
{
|
||||
if (subscriptionInfo?.Subscription is null)
|
||||
if (subscriptionInfo?.Subscription == null)
|
||||
{
|
||||
return expirationDate;
|
||||
// Subscription isn't setup yet, so fallback to the organization's expiration date
|
||||
// If there isn't an expiration date on the org, treat it as a free trial
|
||||
return org.ExpirationDate ?? issued.AddDays(7);
|
||||
}
|
||||
|
||||
var subscription = subscriptionInfo.Subscription;
|
||||
|
||||
if (subscription.TrialEndDate <= DateTime.UtcNow &&
|
||||
org.ExpirationDate >= DateTime.UtcNow &&
|
||||
subscription.PeriodEndDate.HasValue &&
|
||||
subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
if (subscription.TrialEndDate > DateTime.UtcNow)
|
||||
{
|
||||
return subscription.PeriodEndDate.Value;
|
||||
// Still trialing, use trial's end date
|
||||
return subscription.TrialEndDate.Value;
|
||||
}
|
||||
|
||||
return expirationDate;
|
||||
if (org.ExpirationDate < DateTime.UtcNow)
|
||||
{
|
||||
// Organization is expired
|
||||
return org.ExpirationDate.Value;
|
||||
}
|
||||
|
||||
if (subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
{
|
||||
// Annual subscription - refresh every 30 days to check for plan changes, cancellations, and payment issues
|
||||
return issued.AddDays(30);
|
||||
}
|
||||
|
||||
var expires = org.ExpirationDate?.AddMonths(11) ?? issued.AddYears(1);
|
||||
|
||||
// If expiration is more than 30 days in the past, refresh in 30 days instead of using the stale date to give
|
||||
// them a chance to refresh. Otherwise, uses the expiration date
|
||||
return issued - expires > TimeSpan.FromDays(30)
|
||||
? issued.AddDays(30)
|
||||
: expires;
|
||||
}
|
||||
|
||||
public static DateTime? CalculateFreshExpirationDateWithoutGracePeriod(this Organization org, SubscriptionInfo subscriptionInfo)
|
||||
{
|
||||
// It doesn't make sense that this returns null sometimes. If the expiration date doesn't include a grace period
|
||||
// then we should just return the expiration date instead of null. This is currently forcing the single consumer
|
||||
// to check for nulls.
|
||||
|
||||
// At some point in the future, we should update this. We can't easily, though, without breaking the signatures
|
||||
// since `ExpirationWithoutGracePeriod` is included on them. So for now, I'll shake my fist and then move on.
|
||||
|
||||
// Only set expiration without grace period for active, non-trial, annual subscriptions
|
||||
if (subscriptionInfo?.Subscription != null &&
|
||||
subscriptionInfo.Subscription.TrialEndDate <= DateTime.UtcNow &&
|
||||
org.ExpirationDate >= DateTime.UtcNow &&
|
||||
subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
{
|
||||
return subscriptionInfo.Subscription.PeriodEndDate;
|
||||
}
|
||||
|
||||
// Otherwise, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool CalculateIsTrialing(this Organization org, SubscriptionInfo subscriptionInfo) =>
|
||||
subscriptionInfo?.Subscription is null
|
||||
? !org.ExpirationDate.HasValue
|
||||
: subscriptionInfo.Subscription.TrialEndDate > DateTime.UtcNow;
|
||||
|
||||
public static T GetValue<T>(this ClaimsPrincipal principal, string claimType)
|
||||
{
|
||||
var claim = principal.FindFirst(claimType);
|
||||
|
||||
@@ -7,7 +7,6 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Licenses.Extensions;
|
||||
using Bit.Core.Billing.Licenses.Models;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
|
||||
namespace Bit.Core.Billing.Licenses.Services.Implementations;
|
||||
|
||||
@@ -15,11 +14,12 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
||||
{
|
||||
public Task<List<Claim>> GenerateClaims(Organization entity, LicenseContext licenseContext)
|
||||
{
|
||||
var issued = DateTime.UtcNow;
|
||||
var subscriptionInfo = licenseContext.SubscriptionInfo;
|
||||
var expires = entity.CalculateFreshExpirationDate(subscriptionInfo);
|
||||
var refresh = entity.CalculateFreshRefreshDate(subscriptionInfo, expires);
|
||||
var expirationWithoutGracePeriod = entity.CalculateFreshExpirationDateWithoutGracePeriod(subscriptionInfo, expires);
|
||||
var trial = IsTrialing(entity, subscriptionInfo);
|
||||
var expires = entity.CalculateFreshExpirationDate(subscriptionInfo, issued);
|
||||
var refresh = entity.CalculateFreshRefreshDate(subscriptionInfo, issued);
|
||||
var expirationWithoutGracePeriod = entity.CalculateFreshExpirationDateWithoutGracePeriod(subscriptionInfo);
|
||||
var trial = entity.CalculateIsTrialing(subscriptionInfo);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
@@ -50,10 +50,9 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
||||
(entity.LimitCollectionCreation || entity.LimitCollectionDeletion).ToString()),
|
||||
new(nameof(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems), entity.AllowAdminAccessToAllCollectionItems.ToString()),
|
||||
new(nameof(OrganizationLicenseConstants.UseRiskInsights), entity.UseRiskInsights.ToString()),
|
||||
new(nameof(OrganizationLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
|
||||
new(nameof(OrganizationLicenseConstants.Issued), issued.ToString(CultureInfo.InvariantCulture)),
|
||||
new(nameof(OrganizationLicenseConstants.Expires), expires.ToString(CultureInfo.InvariantCulture)),
|
||||
new(nameof(OrganizationLicenseConstants.Refresh), refresh.ToString(CultureInfo.InvariantCulture)),
|
||||
new(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod), expirationWithoutGracePeriod.ToString(CultureInfo.InvariantCulture)),
|
||||
new(nameof(OrganizationLicenseConstants.Trial), trial.ToString()),
|
||||
new(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()),
|
||||
new(nameof(OrganizationLicenseConstants.UseOrganizationDomains), entity.UseOrganizationDomains.ToString()),
|
||||
@@ -113,13 +112,13 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
||||
{
|
||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.SmServiceAccounts), entity.SmServiceAccounts.ToString()));
|
||||
}
|
||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()));
|
||||
|
||||
if (expirationWithoutGracePeriod is not null)
|
||||
{
|
||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod),
|
||||
expirationWithoutGracePeriod.Value.ToString(CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
return Task.FromResult(claims);
|
||||
}
|
||||
|
||||
private static bool IsTrialing(Organization org, SubscriptionInfo subscriptionInfo) =>
|
||||
subscriptionInfo?.Subscription is null
|
||||
? !org.ExpirationDate.HasValue
|
||||
: subscriptionInfo.Subscription.TrialEndDate > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -96,50 +96,13 @@ public class OrganizationLicense : ILicense
|
||||
AllowAdminAccessToAllCollectionItems = org.AllowAdminAccessToAllCollectionItems;
|
||||
//
|
||||
|
||||
if (subscriptionInfo?.Subscription == null)
|
||||
{
|
||||
if (org.ExpirationDate.HasValue)
|
||||
{
|
||||
Expires = Refresh = org.ExpirationDate.Value;
|
||||
Trial = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Expires = Refresh = Issued.AddDays(7);
|
||||
Trial = true;
|
||||
}
|
||||
}
|
||||
else if (subscriptionInfo.Subscription.TrialEndDate.HasValue &&
|
||||
subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow)
|
||||
{
|
||||
Expires = Refresh = subscriptionInfo.Subscription.TrialEndDate.Value;
|
||||
Trial = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (org.ExpirationDate.HasValue && org.ExpirationDate.Value < DateTime.UtcNow)
|
||||
{
|
||||
// expired
|
||||
Expires = Refresh = org.ExpirationDate.Value;
|
||||
}
|
||||
else if (subscriptionInfo?.Subscription?.PeriodDuration != null &&
|
||||
subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180))
|
||||
{
|
||||
Refresh = DateTime.UtcNow.AddDays(30);
|
||||
Expires = subscriptionInfo.Subscription.PeriodEndDate?.AddDays(Core.Constants
|
||||
.OrganizationSelfHostSubscriptionGracePeriodDays);
|
||||
ExpirationWithoutGracePeriod = subscriptionInfo.Subscription.PeriodEndDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
Expires = org.ExpirationDate.HasValue ? org.ExpirationDate.Value.AddMonths(11) : Issued.AddYears(1);
|
||||
Refresh = DateTime.UtcNow - Expires > TimeSpan.FromDays(30) ? DateTime.UtcNow.AddDays(30) : Expires;
|
||||
}
|
||||
|
||||
Trial = false;
|
||||
}
|
||||
|
||||
UseAdminSponsoredFamilies = org.UseAdminSponsoredFamilies;
|
||||
|
||||
Expires = org.CalculateFreshExpirationDate(subscriptionInfo, Issued);
|
||||
Refresh = org.CalculateFreshRefreshDate(subscriptionInfo, Issued);
|
||||
ExpirationWithoutGracePeriod = org.CalculateFreshExpirationDateWithoutGracePeriod(subscriptionInfo);
|
||||
Trial = org.CalculateIsTrialing(subscriptionInfo);
|
||||
|
||||
Hash = Convert.ToBase64String(ComputeHash());
|
||||
Signature = Convert.ToBase64String(licenseService.SignLicense(this));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user