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-11516] Initial license file refactor #5002

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,8 @@
OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey,
string privateKey)
{
var canUse = license.CanUse(_globalSettings, _licensingService, out var exception);
var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license);
var canUse = license.CanUse(_globalSettings, _licensingService, claimsPrincipal, out var exception);

Check warning on line 646 in src/Core/AdminConsole/Services/Implementations/OrganizationService.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/AdminConsole/Services/Implementations/OrganizationService.cs#L645-L646

Added lines #L645 - L646 were not covered by tests
if (!canUse)
{
throw new BadRequestException(exception);
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Billing/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
๏ปฟusing Bit.Core.Billing.Caches;
using Bit.Core.Billing.Caches.Implementations;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Services;
using Bit.Core.Billing.Services.Implementations;

Expand All @@ -15,5 +16,6 @@ public static void AddBillingOperations(this IServiceCollection services)
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
services.AddTransient<ISubscriberService, SubscriberService>();
services.AddLicenseServices();
}
}
151 changes: 151 additions & 0 deletions src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
๏ปฟusing System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Models.Business;

namespace Bit.Core.Billing.Licenses.Extensions;

public static class LicenseExtensions
{
public static DateTime CalculateFreshExpirationDate(this Organization org, SubscriptionInfo subscriptionInfo)
{

Check warning on line 11 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L11

Added line #L11 was not covered by tests
if (subscriptionInfo?.Subscription == null)
{

Check warning on line 13 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L13

Added line #L13 was not covered by tests
if (org.PlanType == PlanType.Custom && org.ExpirationDate.HasValue)
{
return org.ExpirationDate.Value;

Check warning on line 16 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L15-L16

Added lines #L15 - L16 were not covered by tests
}

return DateTime.UtcNow.AddDays(7);

Check warning on line 19 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L19

Added line #L19 was not covered by tests
}

var subscription = subscriptionInfo.Subscription;

Check warning on line 22 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L22

Added line #L22 was not covered by tests

if (subscription.TrialEndDate > DateTime.UtcNow)
{
return subscription.TrialEndDate.Value;

Check warning on line 26 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L25-L26

Added lines #L25 - L26 were not covered by tests
}

if (org.ExpirationDate.HasValue && org.ExpirationDate.Value < DateTime.UtcNow)
{
return org.ExpirationDate.Value;

Check warning on line 31 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L30-L31

Added lines #L30 - L31 were not covered by tests
}

if (subscription.PeriodEndDate.HasValue && subscription.PeriodDuration > TimeSpan.FromDays(180))
{
return subscription.PeriodEndDate
.Value
.AddDays(Bit.Core.Constants.OrganizationSelfHostSubscriptionGracePeriodDays);

Check warning on line 38 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L35-L38

Added lines #L35 - L38 were not covered by tests
}

return org.ExpirationDate?.AddMonths(11) ?? DateTime.UtcNow.AddYears(1);
}

Check warning on line 42 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L42

Added line #L42 was not covered by tests

public static DateTime CalculateFreshRefreshDate(this Organization org, SubscriptionInfo subscriptionInfo, DateTime expirationDate)
{

Check warning on line 45 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L45

Added line #L45 was not covered by tests
if (subscriptionInfo?.Subscription == null ||
subscriptionInfo.Subscription.TrialEndDate > DateTime.UtcNow ||
org.ExpirationDate < DateTime.UtcNow)
{
return expirationDate;

Check warning on line 50 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L47-L50

Added lines #L47 - L50 were not covered by tests
}

return subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180) ||
DateTime.UtcNow - expirationDate > TimeSpan.FromDays(30)
? DateTime.UtcNow.AddDays(30)
: expirationDate;
}

Check warning on line 57 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L54-L57

Added lines #L54 - L57 were not covered by tests

public static DateTime CalculateFreshExpirationDateWithoutGracePeriod(this Organization org, SubscriptionInfo subscriptionInfo, DateTime expirationDate)
{

Check warning on line 60 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L60

Added line #L60 was not covered by tests
if (subscriptionInfo?.Subscription is null)
{
return expirationDate;

Check warning on line 63 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L62-L63

Added lines #L62 - L63 were not covered by tests
}

var subscription = subscriptionInfo.Subscription;

Check warning on line 66 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L66

Added line #L66 was not covered by tests

if (subscription.TrialEndDate <= DateTime.UtcNow &&
org.ExpirationDate >= DateTime.UtcNow &&
subscription.PeriodEndDate.HasValue &&
subscription.PeriodDuration > TimeSpan.FromDays(180))
{
return subscription.PeriodEndDate.Value;

Check warning on line 73 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L69-L73

Added lines #L69 - L73 were not covered by tests
}

return expirationDate;
}

Check warning on line 77 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L76-L77

Added lines #L76 - L77 were not covered by tests

public static T GetValue<T>(this ClaimsPrincipal principal, string claimType)
{
var claim = principal.FindFirst(claimType);

Check warning on line 81 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L80-L81

Added lines #L80 - L81 were not covered by tests

if (claim is null)
{
return default;

Check warning on line 85 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L84-L85

Added lines #L84 - L85 were not covered by tests
}

// Handle Guid
if (typeof(T) == typeof(Guid))
{

Check warning on line 90 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L90

Added line #L90 was not covered by tests
return Guid.TryParse(claim.Value, out var guid)
? (T)(object)guid
: default;

Check warning on line 93 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L92-L93

Added lines #L92 - L93 were not covered by tests
}

// Handle DateTime
if (typeof(T) == typeof(DateTime))
{

Check warning on line 98 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L98

Added line #L98 was not covered by tests
return DateTime.TryParse(claim.Value, out var dateTime)
? (T)(object)dateTime
: default;

Check warning on line 101 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L100-L101

Added lines #L100 - L101 were not covered by tests
}

// Handle TimeSpan
if (typeof(T) == typeof(TimeSpan))
{

Check warning on line 106 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L106

Added line #L106 was not covered by tests
return TimeSpan.TryParse(claim.Value, out var timeSpan)
? (T)(object)timeSpan
: default;

Check warning on line 109 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L108-L109

Added lines #L108 - L109 were not covered by tests
}

// Check for Nullable Types
var underlyingType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);

// Handle Enums
if (underlyingType.IsEnum)
{

Check warning on line 117 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L117

Added line #L117 was not covered by tests
if (Enum.TryParse(underlyingType, claim.Value, true, out var enumValue))
{
return (T)enumValue; // Cast back to T

Check warning on line 120 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L119-L120

Added lines #L119 - L120 were not covered by tests
}

return default; // Return default value for non-nullable enums or null for nullable enums

Check warning on line 123 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L123

Added line #L123 was not covered by tests
}

// Handle other Nullable Types (e.g., int?, bool?)
if (underlyingType == typeof(int))
{

Check warning on line 128 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L128

Added line #L128 was not covered by tests
return int.TryParse(claim.Value, out var intValue)
? (T)(object)intValue
: default;

Check warning on line 131 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L130-L131

Added lines #L130 - L131 were not covered by tests
}

if (underlyingType == typeof(bool))
{

Check warning on line 135 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L135

Added line #L135 was not covered by tests
return bool.TryParse(claim.Value, out var boolValue)
? (T)(object)boolValue
: default;

Check warning on line 138 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L137-L138

Added lines #L137 - L138 were not covered by tests
}

if (underlyingType == typeof(double))
{

Check warning on line 142 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L142

Added line #L142 was not covered by tests
return double.TryParse(claim.Value, out var doubleValue)
? (T)(object)doubleValue
: default;

Check warning on line 145 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L144-L145

Added lines #L144 - L145 were not covered by tests
}

// Fallback to Convert.ChangeType for other types including strings
return (T)Convert.ChangeType(claim.Value, underlyingType);
}

Check warning on line 150 in src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs#L149-L150

Added lines #L149 - L150 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
๏ปฟusing Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Licenses.Services;
using Bit.Core.Billing.Licenses.Services.Implementations;
using Bit.Core.Entities;
using Microsoft.Extensions.DependencyInjection;

namespace Bit.Core.Billing.Licenses.Extensions;

public static class LicenseServiceCollectionExtensions
{
public static void AddLicenseServices(this IServiceCollection services)
{
services.AddTransient<ILicenseClaimsFactory<Organization>, OrganizationLicenseClaimsFactory>();
services.AddTransient<ILicenseClaimsFactory<User>, UserLicenseClaimsFactory>();
}
}
58 changes: 58 additions & 0 deletions src/Core/Billing/Licenses/LicenseConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
๏ปฟnamespace Bit.Core.Billing.Licenses;

public static class OrganizationLicenseConstants
{
public const string LicenseType = nameof(LicenseType);
public const string LicenseKey = nameof(LicenseKey);
public const string InstallationId = nameof(InstallationId);
public const string Id = nameof(Id);
public const string Name = nameof(Name);
public const string BusinessName = nameof(BusinessName);
public const string BillingEmail = nameof(BillingEmail);
public const string Enabled = nameof(Enabled);
public const string Plan = nameof(Plan);
public const string PlanType = nameof(PlanType);
public const string Seats = nameof(Seats);
public const string MaxCollections = nameof(MaxCollections);
public const string UsePolicies = nameof(UsePolicies);
public const string UseSso = nameof(UseSso);
public const string UseKeyConnector = nameof(UseKeyConnector);
public const string UseScim = nameof(UseScim);
public const string UseGroups = nameof(UseGroups);
public const string UseEvents = nameof(UseEvents);
public const string UseDirectory = nameof(UseDirectory);
public const string UseTotp = nameof(UseTotp);
public const string Use2fa = nameof(Use2fa);
public const string UseApi = nameof(UseApi);
public const string UseResetPassword = nameof(UseResetPassword);
public const string MaxStorageGb = nameof(MaxStorageGb);
public const string SelfHost = nameof(SelfHost);
public const string UsersGetPremium = nameof(UsersGetPremium);
public const string UseCustomPermissions = nameof(UseCustomPermissions);
public const string Issued = nameof(Issued);
public const string UsePasswordManager = nameof(UsePasswordManager);
public const string UseSecretsManager = nameof(UseSecretsManager);
public const string SmSeats = nameof(SmSeats);
public const string SmServiceAccounts = nameof(SmServiceAccounts);
public const string LimitCollectionCreationDeletion = nameof(LimitCollectionCreationDeletion);
public const string AllowAdminAccessToAllCollectionItems = nameof(AllowAdminAccessToAllCollectionItems);
public const string Expires = nameof(Expires);
public const string Refresh = nameof(Refresh);
public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod);
public const string Trial = nameof(Trial);
}

public static class UserLicenseConstants
{
public const string LicenseType = nameof(LicenseType);
public const string LicenseKey = nameof(LicenseKey);
public const string Id = nameof(Id);
public const string Name = nameof(Name);
public const string Email = nameof(Email);
public const string Premium = nameof(Premium);
public const string MaxStorageGb = nameof(MaxStorageGb);
public const string Issued = nameof(Issued);
public const string Expires = nameof(Expires);
public const string Refresh = nameof(Refresh);
public const string Trial = nameof(Trial);
}
10 changes: 10 additions & 0 deletions src/Core/Billing/Licenses/Models/LicenseContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
๏ปฟ#nullable enable
using Bit.Core.Models.Business;

namespace Bit.Core.Billing.Licenses.Models;

public class LicenseContext
{
public Guid? InstallationId { get; init; }
public required SubscriptionInfo SubscriptionInfo { get; init; }

Check warning on line 9 in src/Core/Billing/Licenses/Models/LicenseContext.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Models/LicenseContext.cs#L8-L9

Added lines #L8 - L9 were not covered by tests
}
9 changes: 9 additions & 0 deletions src/Core/Billing/Licenses/Services/ILicenseClaimsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
๏ปฟusing System.Security.Claims;
using Bit.Core.Billing.Licenses.Models;

namespace Bit.Core.Billing.Licenses.Services;

public interface ILicenseClaimsFactory<in T>
{
Task<List<Claim>> GenerateClaims(T entity, LicenseContext licenseContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
๏ปฟusing System.Globalization;
using System.Security.Claims;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
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;

public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organization>
{
public Task<List<Claim>> GenerateClaims(Organization entity, LicenseContext licenseContext)
{
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);

Check warning on line 20 in src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs#L15-L20

Added lines #L15 - L20 were not covered by tests

var claims = new List<Claim>
{
new(nameof(OrganizationLicenseConstants.LicenseType), LicenseType.Organization.ToString()),
new Claim(nameof(OrganizationLicenseConstants.LicenseKey), entity.LicenseKey),
new(nameof(OrganizationLicenseConstants.InstallationId), licenseContext.InstallationId.ToString()),
new(nameof(OrganizationLicenseConstants.Id), entity.Id.ToString()),
new(nameof(OrganizationLicenseConstants.Name), entity.Name),
new(nameof(OrganizationLicenseConstants.BillingEmail), entity.BillingEmail),
new(nameof(OrganizationLicenseConstants.Enabled), entity.Enabled.ToString()),
new(nameof(OrganizationLicenseConstants.Plan), entity.Plan),
new(nameof(OrganizationLicenseConstants.PlanType), entity.PlanType.ToString()),
new(nameof(OrganizationLicenseConstants.Seats), entity.Seats.ToString()),
new(nameof(OrganizationLicenseConstants.MaxCollections), entity.MaxCollections.ToString()),
new(nameof(OrganizationLicenseConstants.UsePolicies), entity.UsePolicies.ToString()),
new(nameof(OrganizationLicenseConstants.UseSso), entity.UseSso.ToString()),
new(nameof(OrganizationLicenseConstants.UseKeyConnector), entity.UseKeyConnector.ToString()),
new(nameof(OrganizationLicenseConstants.UseScim), entity.UseScim.ToString()),
new(nameof(OrganizationLicenseConstants.UseGroups), entity.UseGroups.ToString()),
new(nameof(OrganizationLicenseConstants.UseEvents), entity.UseEvents.ToString()),
new(nameof(OrganizationLicenseConstants.UseDirectory), entity.UseDirectory.ToString()),
new(nameof(OrganizationLicenseConstants.UseTotp), entity.UseTotp.ToString()),
new(nameof(OrganizationLicenseConstants.Use2fa), entity.Use2fa.ToString()),
new(nameof(OrganizationLicenseConstants.UseApi), entity.UseApi.ToString()),
new(nameof(OrganizationLicenseConstants.UseResetPassword), entity.UseResetPassword.ToString()),
new(nameof(OrganizationLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString()),
new(nameof(OrganizationLicenseConstants.SelfHost), entity.SelfHost.ToString()),
new(nameof(OrganizationLicenseConstants.UsersGetPremium), entity.UsersGetPremium.ToString()),
new(nameof(OrganizationLicenseConstants.UseCustomPermissions), entity.UseCustomPermissions.ToString()),
new(nameof(OrganizationLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
new(nameof(OrganizationLicenseConstants.UsePasswordManager), entity.UsePasswordManager.ToString()),
new(nameof(OrganizationLicenseConstants.UseSecretsManager), entity.UseSecretsManager.ToString()),
new(nameof(OrganizationLicenseConstants.SmSeats), entity.SmSeats.ToString()),
new(nameof(OrganizationLicenseConstants.SmServiceAccounts), entity.SmServiceAccounts.ToString()),
new(nameof(OrganizationLicenseConstants.LimitCollectionCreationDeletion), entity.LimitCollectionCreationDeletion.ToString()),
new(nameof(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems), entity.AllowAdminAccessToAllCollectionItems.ToString()),
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()),
};

Check warning on line 61 in src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs#L22-L61

Added lines #L22 - L61 were not covered by tests

if (entity.BusinessName is not null)
{
claims.Add(new Claim(nameof(OrganizationLicenseConstants.BusinessName), entity.BusinessName));
}

Check warning on line 66 in src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs#L64-L66

Added lines #L64 - L66 were not covered by tests

return Task.FromResult(claims);
Copy link
Contributor

Choose a reason for hiding this comment

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

โ“ Out of curiosity, what's the purpose of this method returning a Task without doing anything asynchronously?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This came out of a discussion I had with platform on the approach. The idea was to have a single interface that could be used to generate claims for any object. If we needed to have some async IO in some speculative implementation to calculate the value for a claim, then this would allow for that.

}

Check warning on line 69 in src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs#L68-L69

Added lines #L68 - L69 were not covered by tests

private static bool IsTrialing(Organization org, SubscriptionInfo subscriptionInfo) =>
subscriptionInfo?.Subscription is null
? org.PlanType != PlanType.Custom || !org.ExpirationDate.HasValue
: subscriptionInfo.Subscription.TrialEndDate > DateTime.UtcNow;

Check warning on line 74 in src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs#L73-L74

Added lines #L73 - L74 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
๏ปฟusing System.Globalization;
using System.Security.Claims;
using Bit.Core.Billing.Licenses.Models;
using Bit.Core.Entities;
using Bit.Core.Enums;

namespace Bit.Core.Billing.Licenses.Services.Implementations;

public class UserLicenseClaimsFactory : ILicenseClaimsFactory<User>
{
public Task<List<Claim>> GenerateClaims(User entity, LicenseContext licenseContext)
{
var subscriptionInfo = licenseContext.SubscriptionInfo;

Check warning on line 13 in src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs#L12-L13

Added lines #L12 - L13 were not covered by tests

var expires = subscriptionInfo.UpcomingInvoice?.Date?.AddDays(7) ?? entity.PremiumExpirationDate?.AddDays(7);
var refresh = subscriptionInfo.UpcomingInvoice?.Date ?? entity.PremiumExpirationDate;
var trial = (subscriptionInfo.Subscription?.TrialEndDate.HasValue ?? false) &&
subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow;

Check warning on line 18 in src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs#L18

Added line #L18 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

You can probably leverage TimeProvider here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll note it for the next set of reactors in the next PR. In this one, I wanted to keep the logic as identical as possible to how it's working right now, just with the change of using a JWT


var claims = new List<Claim>
{
new(nameof(UserLicenseConstants.LicenseType), LicenseType.User.ToString()),
new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey),
new(nameof(UserLicenseConstants.Id), entity.Id.ToString()),
new(nameof(UserLicenseConstants.Name), entity.Name),
new(nameof(UserLicenseConstants.Email), entity.Email),
new(nameof(UserLicenseConstants.Premium), entity.Premium.ToString()),
new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString()),
new(nameof(UserLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
new(nameof(UserLicenseConstants.Expires), expires.ToString()),
new(nameof(UserLicenseConstants.Refresh), refresh.ToString()),
new(nameof(UserLicenseConstants.Trial), trial.ToString()),
};

Check warning on line 33 in src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs#L20-L33

Added lines #L20 - L33 were not covered by tests

return Task.FromResult(claims);
}

Check warning on line 36 in src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs

View check run for this annotation

Codecov / codecov/patch

src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs#L35-L36

Added lines #L35 - L36 were not covered by tests
}
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public static class FeatureFlagKeys
public const string SecurityTasks = "security-tasks";
public const string PM14401_ScaleMSPOnClientOrganizationUpdate = "PM-14401-scale-msp-on-client-organization-update";
public const string DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship";
public const string SelfHostLicenseRefactor = "pm-11516-self-host-license-refactor";

public static List<string> GetAllKeys()
{
Expand Down
Loading
Loading