1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-26 12:55:17 +01:00

create org with license file

This commit is contained in:
Kyle Spearrin 2017-08-14 20:57:45 -04:00
parent e4ec09fd0c
commit 5259b07889
9 changed files with 146 additions and 60 deletions

View File

@ -395,35 +395,9 @@ namespace Bit.Api.Controllers
var valid = model.Validate(_globalSettings); var valid = model.Validate(_globalSettings);
UserLicense license = null; UserLicense license = null;
if(valid && _globalSettings.SelfHosted && model.License != null) if(valid && _globalSettings.SelfHosted)
{ {
if(!HttpContext.Request.ContentLength.HasValue || HttpContext.Request.ContentLength.Value > 51200) // 50 KB license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
{
valid = false;
}
else
{
try
{
using(var stream = model.License.OpenReadStream())
using(var reader = new StreamReader(stream))
{
var s = await reader.ReadToEndAsync();
if(string.IsNullOrWhiteSpace(s))
{
valid = false;
}
else
{
license = JsonConvert.DeserializeObject<UserLicense>(s);
}
}
}
catch
{
valid = false;
}
}
} }
if(!valid || (_globalSettings.SelfHosted && license == null)) if(!valid || (_globalSettings.SelfHosted && license == null))
@ -488,7 +462,7 @@ namespace Bit.Api.Controllers
[HttpPut("license")] [HttpPut("license")]
[HttpPost("license")] [HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)] [SelfHosted(SelfHostedOnly = true)]
public async Task PutLicense(UpdateLicenseRequestModel model) public async Task PutLicense(LicenseRequestModel model)
{ {
var user = await _userService.GetUserByPrincipalAsync(User); var user = await _userService.GetUserByPrincipalAsync(User);
if(user == null) if(user == null)
@ -496,24 +470,7 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException(); throw new UnauthorizedAccessException();
} }
UserLicense license = null; var license = await ApiHelpers.ReadJsonFileFromBody<UserLicense>(HttpContext, model.License);
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<UserLicense>(s);
}
}
}
catch { }
}
if(license == null) if(license == null)
{ {
throw new BadRequestException("Invalid license"); throw new BadRequestException("Invalid license");

View File

@ -11,6 +11,8 @@ using Bit.Core;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Api.Utilities;
using Bit.Core.Models.Business;
namespace Bit.Api.Controllers namespace Bit.Api.Controllers
{ {
@ -94,6 +96,7 @@ namespace Bit.Api.Controllers
} }
[HttpPost("")] [HttpPost("")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<OrganizationResponseModel> Post([FromBody]OrganizationCreateRequestModel model) public async Task<OrganizationResponseModel> Post([FromBody]OrganizationCreateRequestModel model)
{ {
var user = await _userService.GetUserByPrincipalAsync(User); var user = await _userService.GetUserByPrincipalAsync(User);
@ -107,6 +110,26 @@ namespace Bit.Api.Controllers
return new OrganizationResponseModel(result.Item1); return new OrganizationResponseModel(result.Item1);
} }
[HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)]
public async Task<OrganizationResponseModel> PostLicense(OrganizationCreateLicenseRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if(user == null)
{
throw new UnauthorizedAccessException();
}
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
if(license == null)
{
throw new BadRequestException("Invalid license");
}
var result = await _organizationService.SignUpAsync(license, user, model.Key);
return new OrganizationResponseModel(result.Item1);
}
[HttpPut("{id}")] [HttpPut("{id}")]
[HttpPost("{id}")] [HttpPost("{id}")]
public async Task<OrganizationResponseModel> Put(string id, [FromBody]OrganizationUpdateRequestModel model) public async Task<OrganizationResponseModel> Put(string id, [FromBody]OrganizationUpdateRequestModel model)
@ -132,6 +155,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/payment")] [HttpPut("{id}/payment")]
[HttpPost("{id}/payment")] [HttpPost("{id}/payment")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutPayment(string id, [FromBody]PaymentRequestModel model) public async Task PutPayment(string id, [FromBody]PaymentRequestModel model)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -145,6 +169,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/upgrade")] [HttpPut("{id}/upgrade")]
[HttpPost("{id}/upgrade")] [HttpPost("{id}/upgrade")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutUpgrade(string id, [FromBody]OrganizationUpgradeRequestModel model) public async Task PutUpgrade(string id, [FromBody]OrganizationUpgradeRequestModel model)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -158,6 +183,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/seat")] [HttpPut("{id}/seat")]
[HttpPost("{id}/seat")] [HttpPost("{id}/seat")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutSeat(string id, [FromBody]OrganizationSeatRequestModel model) public async Task PutSeat(string id, [FromBody]OrganizationSeatRequestModel model)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -171,6 +197,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/storage")] [HttpPut("{id}/storage")]
[HttpPost("{id}/storage")] [HttpPost("{id}/storage")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutStorage(string id, [FromBody]StorageRequestModel model) public async Task PutStorage(string id, [FromBody]StorageRequestModel model)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -183,6 +210,7 @@ namespace Bit.Api.Controllers
} }
[HttpPost("{id}/verify-bank")] [HttpPost("{id}/verify-bank")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostVerifyBank(string id, [FromBody]OrganizationVerifyBankRequestModel model) public async Task PostVerifyBank(string id, [FromBody]OrganizationVerifyBankRequestModel model)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -196,6 +224,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/cancel")] [HttpPut("{id}/cancel")]
[HttpPost("{id}/cancel")] [HttpPost("{id}/cancel")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutCancel(string id) public async Task PutCancel(string id)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);
@ -209,6 +238,7 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/reinstate")] [HttpPut("{id}/reinstate")]
[HttpPost("{id}/reinstate")] [HttpPost("{id}/reinstate")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PutReinstate(string id) public async Task PutReinstate(string id)
{ {
var orgIdGuid = new Guid(id); var orgIdGuid = new Guid(id);

View File

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.IO;
using System.Threading.Tasks;
namespace Bit.Api.Utilities
{
public static class ApiHelpers
{
public async static Task<T> ReadJsonFileFromBody<T>(HttpContext httpContext, IFormFile file, long maxSize = 51200)
{
T obj = default(T);
if(file != null && httpContext.Request.ContentLength.HasValue && httpContext.Request.ContentLength.Value <= maxSize)
{
try
{
using(var stream = file.OpenReadStream())
using(var reader = new StreamReader(stream))
{
var s = await reader.ReadToEndAsync();
if(!string.IsNullOrWhiteSpace(s))
{
obj = JsonConvert.DeserializeObject<T>(s);
}
}
}
catch { }
}
return obj;
}
}
}

View File

@ -111,7 +111,7 @@ namespace Bit.Core
public class InstallationSettings public class InstallationSettings
{ {
public Guid? Id { get; set; } public Guid Id { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string IdentityUri { get; set; } public string IdentityUri { get; set; }
} }

View File

@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api namespace Bit.Core.Models.Api
{ {
public class UpdateLicenseRequestModel public class LicenseRequestModel
{ {
[Required] [Required]
public IFormFile License { get; set; } public IFormFile License { get; set; }

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class OrganizationCreateLicenseRequestModel : LicenseRequestModel
{
[Required]
public string Key { get; set; }
}
}

View File

@ -1,5 +1,4 @@
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using System;
namespace Bit.Core.Models.Business namespace Bit.Core.Models.Business
{ {

View File

@ -18,6 +18,7 @@ namespace Bit.Core.Services
Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment); Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2); Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup); Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationLicense license, User owner, string ownerKey);
Task DeleteAsync(Organization organization); Task DeleteAsync(Organization organization);
Task DisableAsync(Guid organizationId, DateTime? expirationDate); Task DisableAsync(Guid organizationId, DateTime? expirationDate);
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate); Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);

View File

@ -26,7 +26,9 @@ namespace Bit.Core.Services
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IPushRegistrationService _pushRegistrationService; private readonly IPushRegistrationService _pushRegistrationService;
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly ILicensingService _licensingService;
private readonly StripePaymentService _stripePaymentService; private readonly StripePaymentService _stripePaymentService;
private readonly GlobalSettings _globalSettings;
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -38,7 +40,9 @@ namespace Bit.Core.Services
IMailService mailService, IMailService mailService,
IPushNotificationService pushNotificationService, IPushNotificationService pushNotificationService,
IPushRegistrationService pushRegistrationService, IPushRegistrationService pushRegistrationService,
IDeviceRepository deviceRepository) IDeviceRepository deviceRepository,
ILicensingService licensingService,
GlobalSettings globalSettings)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -50,7 +54,9 @@ namespace Bit.Core.Services
_pushNotificationService = pushNotificationService; _pushNotificationService = pushNotificationService;
_pushRegistrationService = pushRegistrationService; _pushRegistrationService = pushRegistrationService;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_licensingService = licensingService;
_stripePaymentService = new StripePaymentService(); _stripePaymentService = new StripePaymentService();
_globalSettings = globalSettings;
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken) public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken)
@ -401,11 +407,6 @@ namespace Bit.Core.Services
throw new BadRequestException("Plan not found."); throw new BadRequestException("Plan not found.");
} }
var customerService = new StripeCustomerService();
var subscriptionService = new StripeSubscriptionService();
StripeCustomer customer = null;
StripeSubscription subscription = null;
if(!plan.MaxStorageGb.HasValue && signup.AdditionalStorageGb > 0) if(!plan.MaxStorageGb.HasValue && signup.AdditionalStorageGb > 0)
{ {
throw new BadRequestException("Plan does not allow additional storage."); throw new BadRequestException("Plan does not allow additional storage.");
@ -428,6 +429,11 @@ namespace Bit.Core.Services
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users."); $"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
} }
var customerService = new StripeCustomerService();
var subscriptionService = new StripeSubscriptionService();
StripeCustomer customer = null;
StripeSubscription subscription = null;
// Pre-generate the org id so that we can save it with the Stripe subscription.. // Pre-generate the org id so that we can save it with the Stripe subscription..
Guid newOrgId = CoreHelpers.GenerateComb(); Guid newOrgId = CoreHelpers.GenerateComb();
@ -525,6 +531,52 @@ namespace Bit.Core.Services
RevisionDate = DateTime.UtcNow RevisionDate = DateTime.UtcNow
}; };
return await SignUpAsync(organization, signup.Owner.Id, signup.OwnerKey, true);
}
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(
OrganizationLicense license, User owner, string ownerKey)
{
if(license == null || !_licensingService.VerifyLicense(license) || !license.CanUse(_globalSettings.Installation.Id))
{
throw new BadRequestException("Invalid license.");
}
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled);
if(plan == null)
{
throw new BadRequestException("Plan not found.");
}
var organization = new Organization
{
Name = license.Name,
BillingEmail = null,
BusinessName = null,
PlanType = license.PlanType,
Seats = license.Seats,
MaxCollections = license.MaxCollections,
MaxStorageGb = 10240, // 10 TB
UseGroups = license.UseGroups,
UseDirectory = license.UseDirectory,
UseTotp = license.UseTotp,
Plan = license.Plan,
Gateway = null,
GatewayCustomerId = null,
GatewaySubscriptionId = null,
Enabled = true,
ExpirationDate = license.Expires,
LicenseKey = license.LicenseKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow
};
return await SignUpAsync(organization, owner.Id, ownerKey, false);
}
private async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(Organization organization,
Guid ownerId, string ownerKey, bool withPayment)
{
try try
{ {
await _organizationRepository.CreateAsync(organization); await _organizationRepository.CreateAsync(organization);
@ -532,8 +584,8 @@ namespace Bit.Core.Services
var orgUser = new OrganizationUser var orgUser = new OrganizationUser
{ {
OrganizationId = organization.Id, OrganizationId = organization.Id,
UserId = signup.Owner.Id, UserId = ownerId,
Key = signup.OwnerKey, Key = ownerKey,
Type = OrganizationUserType.Owner, Type = OrganizationUserType.Owner,
Status = OrganizationUserStatusType.Confirmed, Status = OrganizationUserStatusType.Confirmed,
AccessAll = true, AccessAll = true,
@ -546,13 +598,17 @@ namespace Bit.Core.Services
// push // push
var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value); var deviceIds = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds, organization.Id.ToString()); await _pushRegistrationService.AddUserRegistrationOrganizationAsync(deviceIds, organization.Id.ToString());
await _pushNotificationService.PushSyncOrgKeysAsync(signup.Owner.Id); await _pushNotificationService.PushSyncOrgKeysAsync(ownerId);
return new Tuple<Organization, OrganizationUser>(organization, orgUser); return new Tuple<Organization, OrganizationUser>(organization, orgUser);
} }
catch catch
{
if(withPayment)
{ {
await _stripePaymentService.CancelAndRecoverChargesAsync(organization); await _stripePaymentService.CancelAndRecoverChargesAsync(organization);
}
if(organization.Id != default(Guid)) if(organization.Id != default(Guid))
{ {
await _organizationRepository.DeleteAsync(organization); await _organizationRepository.DeleteAsync(organization);