mirror of
https://github.com/bitwarden/server.git
synced 2025-12-10 00:42:07 -06:00
* Refactor license date calculations into extensions * `dotnet format` * Handling case when expirationWithoutGracePeriod is null * Removed extra UseAdminSponsoredFamilies claim
187 lines
6.6 KiB
C#
187 lines
6.6 KiB
C#
// FIXME: Update this file to be null safe and then delete the line below
|
|
#nullable disable
|
|
|
|
using System.Security.Claims;
|
|
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.Models.Business;
|
|
|
|
namespace Bit.Core.Billing.Licenses.Extensions;
|
|
|
|
public static class LicenseExtensions
|
|
{
|
|
public static DateTime CalculateFreshExpirationDate(this Organization org, SubscriptionInfo subscriptionInfo, DateTime issued)
|
|
{
|
|
|
|
if (subscriptionInfo?.Subscription == null)
|
|
{
|
|
// 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 < DateTime.UtcNow)
|
|
{
|
|
// Organization is expired
|
|
return org.ExpirationDate.Value;
|
|
}
|
|
|
|
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(Core.Constants.OrganizationSelfHostSubscriptionGracePeriodDays);
|
|
}
|
|
|
|
// 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 issued)
|
|
{
|
|
|
|
if (subscriptionInfo?.Subscription == null)
|
|
{
|
|
// 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 < 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);
|
|
|
|
if (claim is null)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
// Handle Guid
|
|
if (typeof(T) == typeof(Guid))
|
|
{
|
|
return Guid.TryParse(claim.Value, out var guid)
|
|
? (T)(object)guid
|
|
: default;
|
|
}
|
|
|
|
// Handle DateTime
|
|
if (typeof(T) == typeof(DateTime))
|
|
{
|
|
return DateTime.TryParse(claim.Value, out var dateTime)
|
|
? (T)(object)dateTime
|
|
: default;
|
|
}
|
|
|
|
// Handle TimeSpan
|
|
if (typeof(T) == typeof(TimeSpan))
|
|
{
|
|
return TimeSpan.TryParse(claim.Value, out var timeSpan)
|
|
? (T)(object)timeSpan
|
|
: default;
|
|
}
|
|
|
|
// Check for Nullable Types
|
|
var underlyingType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
|
|
|
// Handle Enums
|
|
if (underlyingType.IsEnum)
|
|
{
|
|
if (Enum.TryParse(underlyingType, claim.Value, true, out var enumValue))
|
|
{
|
|
return (T)enumValue; // Cast back to T
|
|
}
|
|
|
|
return default; // Return default value for non-nullable enums or null for nullable enums
|
|
}
|
|
|
|
// Handle other Nullable Types (e.g., int?, bool?)
|
|
if (underlyingType == typeof(int))
|
|
{
|
|
return int.TryParse(claim.Value, out var intValue)
|
|
? (T)(object)intValue
|
|
: default;
|
|
}
|
|
|
|
if (underlyingType == typeof(bool))
|
|
{
|
|
return bool.TryParse(claim.Value, out var boolValue)
|
|
? (T)(object)boolValue
|
|
: default;
|
|
}
|
|
|
|
if (underlyingType == typeof(double))
|
|
{
|
|
return double.TryParse(claim.Value, out var doubleValue)
|
|
? (T)(object)doubleValue
|
|
: default;
|
|
}
|
|
|
|
// Fallback to Convert.ChangeType for other types including strings
|
|
return (T)Convert.ChangeType(claim.Value, underlyingType);
|
|
}
|
|
}
|