diff --git a/src/Core/Billing/Licenses/Extensions/LicenseServiceCollectionExtensions.cs b/src/Core/Billing/Licenses/Extensions/LicenseServiceCollectionExtensions.cs index c67badae5..b08adbd00 100644 --- a/src/Core/Billing/Licenses/Extensions/LicenseServiceCollectionExtensions.cs +++ b/src/Core/Billing/Licenses/Extensions/LicenseServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ 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; @@ -10,5 +11,6 @@ public static class LicenseServiceCollectionExtensions public static void AddLicenseServices(this IServiceCollection services) { services.AddTransient, OrganizationLicenseClaimsFactory>(); + services.AddTransient, UserLicenseClaimsFactory>(); } } diff --git a/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs new file mode 100644 index 000000000..59e2bd57b --- /dev/null +++ b/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs @@ -0,0 +1,38 @@ +using System.Globalization; +using System.Security.Claims; +using Bit.Core.Billing.Licenses.Models; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Business; + +namespace Bit.Core.Billing.Licenses.Services.Implementations; + +public class UserLicenseClaimsFactory : ILicenseClaimsFactory +{ + public Task> GenerateClaims(User entity, LicenseContext licenseContext) + { + var subscriptionInfo = licenseContext.SubscriptionInfo; + + 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; + + var claims = new List + { + new(nameof(UserLicense.LicenseType), LicenseType.User.ToString()), + new(nameof(UserLicense.LicenseKey), entity.LicenseKey), + new(nameof(UserLicense.Id), entity.Id.ToString()), + new(nameof(UserLicense.Name), entity.Name), + new(nameof(UserLicense.Email), entity.Email), + new(nameof(UserLicense.Premium), entity.Premium.ToString()), + new(nameof(UserLicense.MaxStorageGb), entity.MaxStorageGb.ToString()), + new(nameof(UserLicense.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)), + new(nameof(UserLicense.Expires), expires.ToString()), + new(nameof(UserLicense.Refresh), refresh.ToString()), + new(nameof(UserLicense.Trial), trial.ToString()), + }; + + return Task.FromResult(claims); + } +} diff --git a/src/Core/Models/Business/UserLicense.cs b/src/Core/Models/Business/UserLicense.cs index 0f1b191a1..3e663b0c0 100644 --- a/src/Core/Models/Business/UserLicense.cs +++ b/src/Core/Models/Business/UserLicense.cs @@ -70,6 +70,7 @@ public class UserLicense : ILicense public LicenseType? LicenseType { get; set; } public string Hash { get; set; } public string Signature { get; set; } + public string Token { get; set; } [JsonIgnore] public byte[] SignatureBytes => Convert.FromBase64String(Signature); @@ -84,6 +85,7 @@ public class UserLicense : ILicense !p.Name.Equals(nameof(Signature)) && !p.Name.Equals(nameof(SignatureBytes)) && !p.Name.Equals(nameof(LicenseType)) && + !p.Name.Equals(nameof(Token)) && ( !forHash || ( diff --git a/src/Core/Services/ILicensingService.cs b/src/Core/Services/ILicensingService.cs index ed478c467..7458947d0 100644 --- a/src/Core/Services/ILicensingService.cs +++ b/src/Core/Services/ILicensingService.cs @@ -18,4 +18,6 @@ public interface ILicensingService Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo); + + Task CreateUserTokenAsync(User user, SubscriptionInfo subscriptionInfo); } diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index 4128d72db..9e0580904 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -26,10 +26,10 @@ public class LicensingService : ILicensingService private readonly IGlobalSettings _globalSettings; private readonly IUserRepository _userRepository; private readonly IOrganizationRepository _organizationRepository; - private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IMailService _mailService; private readonly ILogger _logger; private readonly ILicenseClaimsFactory _organizationLicenseClaimsFactory; + private readonly ILicenseClaimsFactory _userLicenseClaimsFactory; private readonly IFeatureService _featureService; private IDictionary _userCheckCache = new Dictionary(); @@ -37,22 +37,22 @@ public class LicensingService : ILicensingService public LicensingService( IUserRepository userRepository, IOrganizationRepository organizationRepository, - IOrganizationUserRepository organizationUserRepository, IMailService mailService, IWebHostEnvironment environment, ILogger logger, IGlobalSettings globalSettings, ILicenseClaimsFactory organizationLicenseClaimsFactory, - IFeatureService featureService) + IFeatureService featureService, + ILicenseClaimsFactory userLicenseClaimsFactory) { _userRepository = userRepository; _organizationRepository = organizationRepository; - _organizationUserRepository = organizationUserRepository; _mailService = mailService; _logger = logger; _globalSettings = globalSettings; _organizationLicenseClaimsFactory = organizationLicenseClaimsFactory; _featureService = featureService; + _userLicenseClaimsFactory = userLicenseClaimsFactory; var certThumbprint = environment.IsDevelopment() ? "207E64A231E8AA32AAF68A61037C075EBEBD553F" : @@ -305,6 +305,21 @@ public class LicensingService : ILicensingService return GenerateToken(claims, audience, expires); } + public async Task CreateUserTokenAsync(User user, SubscriptionInfo subscriptionInfo) + { + if (!_featureService.IsEnabled(FeatureFlagKeys.SelfHostLicenseRefactor)) + { + return null; + } + + var licenseContext = new LicenseContext { SubscriptionInfo = subscriptionInfo }; + var claims = await _userLicenseClaimsFactory.GenerateClaims(user, licenseContext); + var audience = user.Id.ToString(); + var expires = user.PremiumExpirationDate ?? DateTime.UtcNow.AddDays(7); + + return GenerateToken(claims, audience, expires); + } + private string GenerateToken(List claims, string audience, DateTime expires) { if (claims.All(claim => claim.Type != JwtClaimTypes.JwtId)) diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 2199d0a7a..948a580ab 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -1111,7 +1111,9 @@ public class UserService : UserManager, IUserService, IDisposable } } - public async Task GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, + public async Task GenerateLicenseAsync( + User user, + SubscriptionInfo subscriptionInfo = null, int? version = null) { if (user == null) @@ -1124,8 +1126,13 @@ public class UserService : UserManager, IUserService, IDisposable subscriptionInfo = await _paymentService.GetSubscriptionAsync(user); } - return subscriptionInfo == null ? new UserLicense(user, _licenseService) : - new UserLicense(user, subscriptionInfo, _licenseService); + var userLicense = subscriptionInfo == null + ? new UserLicense(user, _licenseService) + : new UserLicense(user, subscriptionInfo, _licenseService); + + userLicense.Token = await _licenseService.CreateUserTokenAsync(user, subscriptionInfo); + + return userLicense; } public override async Task CheckPasswordAsync(User user, string password) diff --git a/src/Core/Services/NoopImplementations/NoopLicensingService.cs b/src/Core/Services/NoopImplementations/NoopLicensingService.cs index 16470e2d5..288840179 100644 --- a/src/Core/Services/NoopImplementations/NoopLicensingService.cs +++ b/src/Core/Services/NoopImplementations/NoopLicensingService.cs @@ -58,4 +58,9 @@ public class NoopLicensingService : ILicensingService { return Task.FromResult(null); } + + public Task CreateUserTokenAsync(User user, SubscriptionInfo subscriptionInfo) + { + return Task.FromResult(null); + } }