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.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);

View File

@ -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)

View File

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

View File

@ -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<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 Bit.Core.Billing.Licenses.Attributes;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Entities;
using Bit.Core.Services;
namespace Bit.Core.Models.Business;
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 +20,4 @@ public class UserLicense : BaseLicense
{
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.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<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)
{
services.AddScoped<ICreateOrganizationDomainCommand, CreateOrganizationDomainCommand>();

View File

@ -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<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
int? version = null);
Task<bool> CheckPasswordAsync(User user, string password);
/// <summary>
/// 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)
{
if (user == null)