From 18cbc79dd20a1340bb83fa753e56fb8a1fc0c19c Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 14 Aug 2017 12:08:57 -0400 Subject: [PATCH] update premium license and self host attr checks --- src/Api/Controllers/AccountsController.cs | 42 +++++++++++++++++++ src/Api/Utilities/SelfHostedAttribute.cs | 26 ++++++++++++ .../Accounts/UpdateLicenseRequestModel.cs | 11 +++++ .../Api/Response/BillingResponseModel.cs | 3 ++ src/Core/Services/IUserService.cs | 1 + .../Services/Implementations/UserService.cs | 24 +++++++++++ 6 files changed, 107 insertions(+) create mode 100644 src/Api/Utilities/SelfHostedAttribute.cs create mode 100644 src/Core/Models/Api/Request/Accounts/UpdateLicenseRequestModel.cs diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index e13d33087c..4600e62d27 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -15,6 +15,7 @@ using Bit.Core; using System.IO; using Newtonsoft.Json; using Bit.Core.Models.Business; +using Bit.Api.Utilities; namespace Bit.Api.Controllers { @@ -458,6 +459,7 @@ namespace Bit.Api.Controllers [HttpPut("payment")] [HttpPost("payment")] + [SelfHosted(NotSelfHostedOnly = true)] public async Task PutPayment([FromBody]PaymentRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); @@ -471,6 +473,7 @@ namespace Bit.Api.Controllers [HttpPut("storage")] [HttpPost("storage")] + [SelfHosted(NotSelfHostedOnly = true)] public async Task PutStorage([FromBody]StorageRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); @@ -482,8 +485,46 @@ namespace Bit.Api.Controllers await _userService.AdjustStorageAsync(user, model.StorageGbAdjustment.Value); } + [HttpPut("license")] + [HttpPost("license")] + [SelfHosted(SelfHostedOnly = true)] + public async Task PutLicense(UpdateLicenseRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if(user == null) + { + throw new UnauthorizedAccessException(); + } + + UserLicense license = null; + if(HttpContext.Request.ContentLength.HasValue && HttpContext.Request.ContentLength.Value <= 51200) // 50 KB + { + try + { + using(var stream = model.License.OpenReadStream()) + using(var reader = new StreamReader(stream)) + { + var s = await reader.ReadToEndAsync(); + if(!string.IsNullOrWhiteSpace(s)) + { + license = JsonConvert.DeserializeObject(s); + } + } + } + catch { } + } + + if(license == null) + { + throw new BadRequestException("Invalid license"); + } + + await _userService.UpdateLicenseAsync(user, license); + } + [HttpPut("cancel-premium")] [HttpPost("cancel-premium")] + [SelfHosted(NotSelfHostedOnly = true)] public async Task PutCancel() { var user = await _userService.GetUserByPrincipalAsync(User); @@ -497,6 +538,7 @@ namespace Bit.Api.Controllers [HttpPut("reinstate-premium")] [HttpPost("reinstate-premium")] + [SelfHosted(NotSelfHostedOnly = true)] public async Task PutReinstate() { var user = await _userService.GetUserByPrincipalAsync(User); diff --git a/src/Api/Utilities/SelfHostedAttribute.cs b/src/Api/Utilities/SelfHostedAttribute.cs new file mode 100644 index 0000000000..cc199142ff --- /dev/null +++ b/src/Api/Utilities/SelfHostedAttribute.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Bit.Core; +using Bit.Core.Exceptions; + +namespace Bit.Api.Utilities +{ + public class SelfHostedAttribute : ActionFilterAttribute + { + public bool SelfHostedOnly { get; set; } + public bool NotSelfHostedOnly { get; set; } + + public override void OnActionExecuting(ActionExecutingContext context) + { + var globalSettings = context.HttpContext.RequestServices.GetRequiredService(); + if(SelfHostedOnly && !globalSettings.SelfHosted) + { + throw new BadRequestException("Only allowed when self hosted."); + } + else if(NotSelfHostedOnly && globalSettings.SelfHosted) + { + throw new BadRequestException("Only allowed when not self hosted."); + } + } + } +} diff --git a/src/Core/Models/Api/Request/Accounts/UpdateLicenseRequestModel.cs b/src/Core/Models/Api/Request/Accounts/UpdateLicenseRequestModel.cs new file mode 100644 index 0000000000..5eb2a53b43 --- /dev/null +++ b/src/Core/Models/Api/Request/Accounts/UpdateLicenseRequestModel.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Http; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api +{ + public class UpdateLicenseRequestModel + { + [Required] + public IFormFile License { get; set; } + } +} diff --git a/src/Core/Models/Api/Response/BillingResponseModel.cs b/src/Core/Models/Api/Response/BillingResponseModel.cs index cea98a8367..2f6a17ef44 100644 --- a/src/Core/Models/Api/Response/BillingResponseModel.cs +++ b/src/Core/Models/Api/Response/BillingResponseModel.cs @@ -21,6 +21,7 @@ namespace Bit.Core.Models.Api StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB MaxStorageGb = user.MaxStorageGb; License = new UserLicense(user, billing, licenseService); + Expiration = License.Expires; } public BillingResponseModel(User user) @@ -29,6 +30,7 @@ namespace Bit.Core.Models.Api StorageName = user.Storage.HasValue ? Utilities.CoreHelpers.ReadableBytesSize(user.Storage.Value) : null; StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB MaxStorageGb = user.MaxStorageGb; + Expiration = user.PremiumExpirationDate; } public string StorageName { get; set; } @@ -39,6 +41,7 @@ namespace Bit.Core.Models.Api public BillingInvoice UpcomingInvoice { get; set; } public IEnumerable Charges { get; set; } public UserLicense License { get; set; } + public DateTime? Expiration { get; set; } } public class BillingSource diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index fc998e86c7..e7dd286a2a 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -41,6 +41,7 @@ namespace Bit.Core.Services Task DeleteAsync(User user, string token); Task SendDeleteConfirmationAsync(string email); Task SignUpPremiumAsync(User user, string paymentToken, short additionalStorageGb, UserLicense license); + Task UpdateLicenseAsync(User user, UserLicense license); Task AdjustStorageAsync(User user, short storageAdjustmentGb); Task ReplacePaymentMethodAsync(User user, string paymentToken); Task CancelPremiumAsync(User user, bool endOfPeriod = false); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 81bd0d0e8a..5556f5f7ef 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -598,6 +598,30 @@ namespace Bit.Core.Services } } + public async Task UpdateLicenseAsync(User user, UserLicense license) + { + if(!_globalSettings.SelfHosted) + { + throw new InvalidOperationException("Licenses require self hosting."); + } + + if(license == null || !_licenseService.VerifyLicense(license)) + { + throw new BadRequestException("Invalid license."); + } + + var dir = $"{_globalSettings.LicenseDirectory}/user"; + Directory.CreateDirectory(dir); + File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented)); + + user.Premium = true; + user.RevisionDate = DateTime.UtcNow; + user.MaxStorageGb = 10240; // 10 TB + user.LicenseKey = license.LicenseKey; + user.PremiumExpirationDate = license.Expires; + await SaveUserAsync(user); + } + public async Task AdjustStorageAsync(User user, short storageAdjustmentGb) { if(user == null)