1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-15 15:30:49 -05:00

Moved user license out of user service

This commit is contained in:
Conner Turnbull 2025-06-11 13:29:04 -04:00
parent 1f90def09e
commit b02e47eed7
No known key found for this signature in database
10 changed files with 97 additions and 93 deletions

View File

@ -5,6 +5,7 @@ using Bit.Admin.Models;
using Bit.Admin.Utilities; using Bit.Admin.Utilities;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.BitStripe; using Bit.Core.Models.BitStripe;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
@ -25,6 +26,7 @@ public class ToolsController : Controller
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IGetUserLicenseQuery _getUserLicenseQuery;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly ITransactionRepository _transactionRepository; private readonly ITransactionRepository _transactionRepository;
private readonly IInstallationRepository _installationRepository; private readonly IInstallationRepository _installationRepository;
@ -38,6 +40,7 @@ public class ToolsController : Controller
GlobalSettings globalSettings, GlobalSettings globalSettings,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IGetUserLicenseQuery getUserLicenseQuery,
IUserService userService, IUserService userService,
ITransactionRepository transactionRepository, ITransactionRepository transactionRepository,
IInstallationRepository installationRepository, IInstallationRepository installationRepository,
@ -50,6 +53,7 @@ public class ToolsController : Controller
_globalSettings = globalSettings; _globalSettings = globalSettings;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_getUserLicenseQuery = getUserLicenseQuery;
_userService = userService; _userService = userService;
_transactionRepository = transactionRepository; _transactionRepository = transactionRepository;
_installationRepository = installationRepository; _installationRepository = installationRepository;
@ -326,7 +330,7 @@ public class ToolsController : Controller
} }
else if (user != null) 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(); var ms = new MemoryStream();
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented); await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented);

View File

@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Accounts;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Utilities; using Bit.Api.Utilities;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Billing.Models; using Bit.Core.Billing.Models;
using Bit.Core.Billing.Services; using Bit.Core.Billing.Services;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -20,6 +21,7 @@ namespace Bit.Api.Billing.Controllers;
[Authorize("Application")] [Authorize("Application")]
public class AccountsController( public class AccountsController(
IUserService userService, IUserService userService,
IGetUserLicenseQuery getUserLicenseQuery,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) : Controller ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) : Controller
{ {
[HttpPost("premium")] [HttpPost("premium")]
@ -82,12 +84,12 @@ public class AccountsController(
if (!globalSettings.SelfHosted && user.Gateway != null) if (!globalSettings.SelfHosted && user.Gateway != null)
{ {
var subscriptionInfo = await paymentService.GetSubscriptionAsync(user); 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); return new SubscriptionResponseModel(user, subscriptionInfo, license);
} }
else if (!globalSettings.SelfHosted) else if (!globalSettings.SelfHosted)
{ {
var license = await userService.GenerateLicenseAsync(user); var license = await getUserLicenseQuery.GetLicenseAsync(user);
return new SubscriptionResponseModel(user, license); return new SubscriptionResponseModel(user, license);
} }
else else
@ -133,8 +135,6 @@ public class AccountsController(
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result }; return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
} }
[HttpPost("license")] [HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)] [SelfHosted(SelfHostedOnly = true)]
public async Task PostLicenseAsync(LicenseRequestModel model) public async Task PostLicenseAsync(LicenseRequestModel model)

View File

@ -1,11 +1,11 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Api.OrganizationLicenses; using Bit.Core.Models.Api.OrganizationLicenses;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -18,7 +18,7 @@ namespace Bit.Api.Controllers;
public class LicensesController : Controller public class LicensesController : Controller
{ {
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IUserService _userService; private readonly IGetUserLicenseQuery _getUserLicenseQuery;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand; private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
@ -26,14 +26,14 @@ public class LicensesController : Controller
public LicensesController( public LicensesController(
IUserRepository userRepository, IUserRepository userRepository,
IUserService userService, IGetUserLicenseQuery getUserLicenseQuery,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand, IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICurrentContext currentContext) ICurrentContext currentContext)
{ {
_userRepository = userRepository; _userRepository = userRepository;
_userService = userService; _getUserLicenseQuery = getUserLicenseQuery;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_validateBillingSyncKeyCommand = validateBillingSyncKeyCommand; _validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
@ -54,7 +54,7 @@ public class LicensesController : Controller
throw new BadRequestException("Invalid license key."); throw new BadRequestException("Invalid license key.");
} }
var license = await _userService.GenerateLicenseAsync(user, null); var license = await _getUserLicenseQuery.GetLicenseAsync(user, null);
return license; return license;
} }

View File

@ -10,6 +10,10 @@ using Bit.Core.Billing.Tax.Services.Implementations;
namespace Bit.Core.Billing.Extensions; 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; using Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
@ -27,5 +31,14 @@ public static class ServiceCollectionExtensions
services.AddLicenseServices(); services.AddLicenseServices();
services.AddPricingClient(); services.AddPricingClient();
services.AddTransient<IPreviewTaxAmountCommand, PreviewTaxAmountCommand>(); services.AddTransient<IPreviewTaxAmountCommand, PreviewTaxAmountCommand>();
services.AddOrganizationLicenseCommandsQueries();
}
private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services)
{
services.AddScoped<ICloudGetOrganizationLicenseQuery, CloudGetOrganizationLicenseQuery>();
services.AddScoped<ISelfHostedGetOrganizationLicenseQuery, SelfHostedGetOrganizationLicenseQuery>();
services.AddScoped<IUpdateOrganizationLicenseCommand, UpdateOrganizationLicenseCommand>();
services.AddScoped<IGetUserLicenseQuery, GetUserLicenseQuery>();
} }
} }

View File

@ -0,0 +1,9 @@
using Bit.Core.Entities;
using Bit.Core.Models.Business;
namespace Bit.Core.Billing.Licenses.Queries;
public interface IGetUserLicenseQuery
{
Task<UserLicense> GetLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, int? version = null);
}

View File

@ -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<UserLicense> 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;
}
}

View File

@ -1,55 +1,10 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Bit.Core.Billing.Licenses.Attributes; 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; namespace Bit.Core.Models.Business;
public class UserLicense : BaseLicense 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)] [LicenseVersion(1)]
public string Email { get; set; } public string Email { get; set; }
@ -65,6 +20,4 @@ public class UserLicense : BaseLicense
{ {
get => Version == 1; get => Version == 1;
} }
} }

View File

@ -22,8 +22,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v
using Bit.Core.Models.Business.Tokenables; using Bit.Core.Models.Business.Tokenables;
using Bit.Core.OrganizationFeatures.OrganizationCollections; using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; 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;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
@ -56,7 +54,6 @@ public static class OrganizationServiceCollectionExtensions
services.AddOrganizationApiKeyCommandsQueries(); services.AddOrganizationApiKeyCommandsQueries();
services.AddOrganizationCollectionCommands(); services.AddOrganizationCollectionCommands();
services.AddOrganizationGroupCommands(); services.AddOrganizationGroupCommands();
services.AddOrganizationLicenseCommandsQueries();
services.AddOrganizationDomainCommandsQueries(); services.AddOrganizationDomainCommandsQueries();
services.AddOrganizationSignUpCommands(); services.AddOrganizationSignUpCommands();
services.AddOrganizationDeleteCommands(); services.AddOrganizationDeleteCommands();
@ -154,13 +151,6 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IUpdateGroupCommand, UpdateGroupCommand>(); services.AddScoped<IUpdateGroupCommand, UpdateGroupCommand>();
} }
private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services)
{
services.AddScoped<ICloudGetOrganizationLicenseQuery, CloudGetOrganizationLicenseQuery>();
services.AddScoped<ISelfHostedGetOrganizationLicenseQuery, SelfHostedGetOrganizationLicenseQuery>();
services.AddScoped<IUpdateOrganizationLicenseCommand, UpdateOrganizationLicenseCommand>();
}
private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services) private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services)
{ {
services.AddScoped<ICreateOrganizationDomainCommand, CreateOrganizationDomainCommand>(); services.AddScoped<ICreateOrganizationDomainCommand, CreateOrganizationDomainCommand>();

View File

@ -68,8 +68,7 @@ public interface IUserService
Task EnablePremiumAsync(Guid userId, DateTime? expirationDate); Task EnablePremiumAsync(Guid userId, DateTime? expirationDate);
Task DisablePremiumAsync(Guid userId, DateTime? expirationDate); Task DisablePremiumAsync(Guid userId, DateTime? expirationDate);
Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate);
Task<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
int? version = null);
Task<bool> CheckPasswordAsync(User user, string password); Task<bool> CheckPasswordAsync(User user, string password);
/// <summary> /// <summary>
/// Checks if the user has access to premium features, either through a personal subscription or through an organization. /// Checks if the user has access to premium features, either through a personal subscription or through an organization.

View File

@ -1183,30 +1183,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
} }
} }
public async Task<UserLicense> 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<bool> CheckPasswordAsync(User user, string password) public override async Task<bool> CheckPasswordAsync(User user, string password)
{ {
if (user == null) if (user == null)