diff --git a/src/Admin/Controllers/ToolsController.cs b/src/Admin/Controllers/ToolsController.cs index eaf3de4be5..30d01d2d70 100644 --- a/src/Admin/Controllers/ToolsController.cs +++ b/src/Admin/Controllers/ToolsController.cs @@ -5,6 +5,7 @@ using Bit.Admin.Models; using Bit.Admin.Utilities; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Licenses.Queries; using Bit.Core.Entities; using Bit.Core.Models.BitStripe; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; @@ -25,6 +26,7 @@ public class ToolsController : Controller private readonly GlobalSettings _globalSettings; private readonly IOrganizationRepository _organizationRepository; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; + private readonly IGetUserLicenseQuery _getUserLicenseQuery; private readonly IUserService _userService; private readonly ITransactionRepository _transactionRepository; private readonly IInstallationRepository _installationRepository; @@ -38,6 +40,7 @@ public class ToolsController : Controller GlobalSettings globalSettings, IOrganizationRepository organizationRepository, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, + IGetUserLicenseQuery getUserLicenseQuery, IUserService userService, ITransactionRepository transactionRepository, IInstallationRepository installationRepository, @@ -50,6 +53,7 @@ public class ToolsController : Controller _globalSettings = globalSettings; _organizationRepository = organizationRepository; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; + _getUserLicenseQuery = getUserLicenseQuery; _userService = userService; _transactionRepository = transactionRepository; _installationRepository = installationRepository; @@ -326,7 +330,7 @@ public class ToolsController : Controller } else if (user != null) { - var license = await _userService.GenerateLicenseAsync(user, null, model.Version); + var license = await _getUserLicenseQuery.GetLicenseAsync(user, null, model.Version); var ms = new MemoryStream(); ms.Seek(0, SeekOrigin.Begin); await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented); diff --git a/src/Api/Billing/Controllers/AccountsController.cs b/src/Api/Billing/Controllers/AccountsController.cs index 10d386641d..22363fdee1 100644 --- a/src/Api/Billing/Controllers/AccountsController.cs +++ b/src/Api/Billing/Controllers/AccountsController.cs @@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Response; using Bit.Api.Utilities; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Billing.Licenses.Queries; using Bit.Core.Billing.Models; using Bit.Core.Billing.Services; using Bit.Core.Exceptions; @@ -20,6 +21,7 @@ namespace Bit.Api.Billing.Controllers; [Authorize("Application")] public class AccountsController( IUserService userService, + IGetUserLicenseQuery getUserLicenseQuery, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) : Controller { [HttpPost("premium")] @@ -82,12 +84,12 @@ public class AccountsController( if (!globalSettings.SelfHosted && user.Gateway != null) { var subscriptionInfo = await paymentService.GetSubscriptionAsync(user); - var license = await userService.GenerateLicenseAsync(user, subscriptionInfo); + var license = await getUserLicenseQuery.GetLicenseAsync(user, subscriptionInfo); return new SubscriptionResponseModel(user, subscriptionInfo, license); } else if (!globalSettings.SelfHosted) { - var license = await userService.GenerateLicenseAsync(user); + var license = await getUserLicenseQuery.GetLicenseAsync(user); return new SubscriptionResponseModel(user, license); } else @@ -133,8 +135,6 @@ public class AccountsController( return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result }; } - - [HttpPost("license")] [SelfHosted(SelfHostedOnly = true)] public async Task PostLicenseAsync(LicenseRequestModel model) diff --git a/src/Api/Controllers/LicensesController.cs b/src/Api/Controllers/LicensesController.cs index 1c00589201..355a825f53 100644 --- a/src/Api/Controllers/LicensesController.cs +++ b/src/Api/Controllers/LicensesController.cs @@ -1,11 +1,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces; +using Bit.Core.Billing.Licenses.Queries; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Api.OrganizationLicenses; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,7 +18,7 @@ namespace Bit.Api.Controllers; public class LicensesController : Controller { private readonly IUserRepository _userRepository; - private readonly IUserService _userService; + private readonly IGetUserLicenseQuery _getUserLicenseQuery; private readonly IOrganizationRepository _organizationRepository; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand; @@ -26,14 +26,14 @@ public class LicensesController : Controller public LicensesController( IUserRepository userRepository, - IUserService userService, + IGetUserLicenseQuery getUserLicenseQuery, IOrganizationRepository organizationRepository, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand, ICurrentContext currentContext) { _userRepository = userRepository; - _userService = userService; + _getUserLicenseQuery = getUserLicenseQuery; _organizationRepository = organizationRepository; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; _validateBillingSyncKeyCommand = validateBillingSyncKeyCommand; @@ -54,7 +54,7 @@ public class LicensesController : Controller throw new BadRequestException("Invalid license key."); } - var license = await _userService.GenerateLicenseAsync(user, null); + var license = await _getUserLicenseQuery.GetLicenseAsync(user, null); return license; } diff --git a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs index 5c7a42e9b8..dcba7aea52 100644 --- a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs +++ b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs @@ -10,6 +10,10 @@ using Bit.Core.Billing.Tax.Services.Implementations; namespace Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Licenses.Queries; +using Bit.Core.Billing.Licenses.Queries.Implementations; +using Bit.Core.OrganizationFeatures.OrganizationLicenses; +using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions @@ -27,5 +31,14 @@ public static class ServiceCollectionExtensions services.AddLicenseServices(); services.AddPricingClient(); services.AddTransient(); + services.AddOrganizationLicenseCommandsQueries(); + } + + private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Core/Billing/Licenses/Queries/IGetUserLicenseQuery.cs b/src/Core/Billing/Licenses/Queries/IGetUserLicenseQuery.cs new file mode 100644 index 0000000000..6043ad5807 --- /dev/null +++ b/src/Core/Billing/Licenses/Queries/IGetUserLicenseQuery.cs @@ -0,0 +1,9 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Business; + +namespace Bit.Core.Billing.Licenses.Queries; + +public interface IGetUserLicenseQuery +{ + Task GetLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, int? version = null); +} diff --git a/src/Core/Billing/Licenses/Queries/Implementations/GetUserLicenseQuery.cs b/src/Core/Billing/Licenses/Queries/Implementations/GetUserLicenseQuery.cs new file mode 100644 index 0000000000..11661ba32b --- /dev/null +++ b/src/Core/Billing/Licenses/Queries/Implementations/GetUserLicenseQuery.cs @@ -0,0 +1,60 @@ +using Bit.Core.Billing.Licenses.Extensions; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Business; +using Bit.Core.Services; + +namespace Bit.Core.Billing.Licenses.Queries.Implementations; + +public class GetUserLicenseQuery : IGetUserLicenseQuery +{ + private readonly IPaymentService _paymentService; + private readonly ILicensingService _licensingService; + + public GetUserLicenseQuery( + IPaymentService paymentService, + ILicensingService licensingService) + { + _paymentService = paymentService; + _licensingService = licensingService; + } + + public async Task GetLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, int? version = null) + { + if (user == null) + { + throw new NotFoundException(); + } + + if (subscriptionInfo == null && user.Gateway != null) + { + subscriptionInfo = await _paymentService.GetSubscriptionAsync(user); + } + + var issued = DateTime.UtcNow; + + var userLicense = new UserLicense + { + Version = version.GetValueOrDefault(1), + LicenseType = LicenseType.User, + LicenseKey = user.LicenseKey, + Id = user.Id, + Name = user.Name, + Email = user.Email, + Premium = user.Premium, + MaxStorageGb = user.MaxStorageGb, + Issued = issued, + Expires = user.CalculateFreshExpirationDate(subscriptionInfo), + Refresh = user.CalculateFreshRefreshDate(subscriptionInfo), + Trial = user.IsTrialing(subscriptionInfo) + }; + + // Hash is included in Signature, and so must be initialized before signing + userLicense.Hash = Convert.ToBase64String(userLicense.ComputeHash()); + userLicense.Signature = Convert.ToBase64String(_licensingService.SignLicense(userLicense)); + userLicense.Token = await _licensingService.CreateUserTokenAsync(user, subscriptionInfo); + + return userLicense; + } +} diff --git a/src/Core/Models/Business/UserLicense.cs b/src/Core/Models/Business/UserLicense.cs index 4ba37eab55..83f167ffa8 100644 --- a/src/Core/Models/Business/UserLicense.cs +++ b/src/Core/Models/Business/UserLicense.cs @@ -1,8 +1,5 @@ using System.Text.Json.Serialization; using Bit.Core.Billing.Licenses.Attributes; -using Bit.Core.Billing.Licenses.Extensions; -using Bit.Core.Entities; -using Bit.Core.Services; namespace Bit.Core.Models.Business; @@ -11,45 +8,6 @@ public class UserLicense : BaseLicense public UserLicense() { } - public UserLicense(User user, SubscriptionInfo subscriptionInfo, ILicensingService licenseService, - int? version = null) - { - LicenseType = Enums.LicenseType.User; - LicenseKey = user.LicenseKey; - Id = user.Id; - Name = user.Name; - Email = user.Email; - Version = version.GetValueOrDefault(1); - Premium = user.Premium; - MaxStorageGb = user.MaxStorageGb; - Issued = DateTime.UtcNow; - Expires = user.CalculateFreshExpirationDate(subscriptionInfo); - Refresh = user.CalculateFreshRefreshDate(subscriptionInfo); - Trial = user.IsTrialing(subscriptionInfo); - - Hash = Convert.ToBase64String(this.ComputeHash()); - Signature = Convert.ToBase64String(licenseService.SignLicense(this)); - } - - public UserLicense(User user, ILicensingService licenseService, int? version = null) - { - LicenseType = Enums.LicenseType.User; - LicenseKey = user.LicenseKey; - Id = user.Id; - Name = user.Name; - Email = user.Email; - Version = version.GetValueOrDefault(1); - Premium = user.Premium; - MaxStorageGb = user.MaxStorageGb; - Issued = DateTime.UtcNow; - Expires = user.CalculateFreshExpirationDate(null); - Refresh = user.CalculateFreshRefreshDate(null); - Trial = user.IsTrialing(null); - - Hash = Convert.ToBase64String(this.ComputeHash()); - Signature = Convert.ToBase64String(licenseService.SignLicense(this)); - } - [LicenseVersion(1)] public string Email { get; set; } @@ -65,6 +23,4 @@ public class UserLicense : BaseLicense { get => Version == 1; } - - } diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 2bc05017d5..c338b08e40 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -22,8 +22,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v using Bit.Core.Models.Business.Tokenables; using Bit.Core.OrganizationFeatures.OrganizationCollections; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; -using Bit.Core.OrganizationFeatures.OrganizationLicenses; -using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; @@ -56,7 +54,6 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationApiKeyCommandsQueries(); services.AddOrganizationCollectionCommands(); services.AddOrganizationGroupCommands(); - services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationDomainCommandsQueries(); services.AddOrganizationSignUpCommands(); services.AddOrganizationDeleteCommands(); @@ -154,13 +151,6 @@ public static class OrganizationServiceCollectionExtensions services.AddScoped(); } - private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - } - private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services) { services.AddScoped(); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index e63b4e3b87..41ab4868a7 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -68,8 +68,7 @@ public interface IUserService Task EnablePremiumAsync(Guid userId, DateTime? expirationDate); Task DisablePremiumAsync(Guid userId, DateTime? expirationDate); Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); - Task GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, - int? version = null); + Task CheckPasswordAsync(User user, string password); /// /// Checks if the user has access to premium features, either through a personal subscription or through an organization. diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 265adf59bf..8dd62ea98b 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -1183,30 +1183,6 @@ public class UserService : UserManager, IUserService, IDisposable } } - public async Task GenerateLicenseAsync( - User user, - SubscriptionInfo subscriptionInfo = null, - int? version = null) - { - if (user == null) - { - throw new NotFoundException(); - } - - if (subscriptionInfo == null && user.Gateway != null) - { - subscriptionInfo = await _paymentService.GetSubscriptionAsync(user); - } - - 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) { if (user == null)