1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-01 23:31:41 +01:00

Add autoscale to SSO auto provision (#1642)

This commit is contained in:
Matt Gibson 2021-10-19 07:00:44 -05:00 committed by GitHub
parent 86755e130c
commit 18006591fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 12 deletions

View File

@ -25,6 +25,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Utilities;
using System.Text.Json;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
namespace Bit.Sso.Controllers
{
@ -37,6 +38,7 @@ namespace Bit.Sso.Controllers
private readonly ILogger<AccountController> _logger;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IUserRepository _userRepository;
@ -44,6 +46,7 @@ namespace Bit.Sso.Controllers
private readonly IUserService _userService;
private readonly II18nService _i18nService;
private readonly UserManager<User> _userManager;
private readonly IGlobalSettings _globalSettings;
private readonly Core.Services.IEventService _eventService;
public AccountController(
@ -53,6 +56,7 @@ namespace Bit.Sso.Controllers
ILogger<AccountController> logger,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService,
ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository,
IUserRepository userRepository,
@ -60,6 +64,7 @@ namespace Bit.Sso.Controllers
IUserService userService,
II18nService i18nService,
UserManager<User> userManager,
IGlobalSettings globalSettings,
Core.Services.IEventService eventService)
{
_schemeProvider = schemeProvider;
@ -68,6 +73,7 @@ namespace Bit.Sso.Controllers
_logger = logger;
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
_organizationService = organizationService;
_userRepository = userRepository;
_ssoConfigRepository = ssoConfigRepository;
_ssoUserRepository = ssoUserRepository;
@ -76,6 +82,7 @@ namespace Bit.Sso.Controllers
_i18nService = i18nService;
_userManager = userManager;
_eventService = eventService;
_globalSettings = globalSettings;
}
[HttpGet]
@ -469,12 +476,36 @@ namespace Bit.Sso.Controllers
if (orgUser == null && organization.Seats.HasValue)
{
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(orgId);
var availableSeats = organization.Seats.Value - userCount;
var initialSeatCount = organization.Seats.Value;
var availableSeats = initialSeatCount - userCount;
var prorationDate = DateTime.UtcNow;
if (availableSeats < 1)
{
try
{
if (_globalSettings.SelfHosted)
{
throw new Exception("Cannot autoscale on self-hosted instance.");
}
var paymentIntentClientSecret = await _organizationService.AdjustSeatsAsync(orgId, 1, prorationDate);
organization = await _organizationRepository.GetByIdAsync(orgId);
if (!string.IsNullOrEmpty(paymentIntentClientSecret))
{
throw new Exception("Stripe payment required client-side confirmation.");
}
}
catch (Exception e)
{
if (organization.Seats.Value != initialSeatCount)
{
await _organizationService.AdjustSeatsAsync(orgId, initialSeatCount - organization.Seats.Value, prorationDate);
}
_logger.LogInformation(e, "SSO auto provisioning failed");
throw new Exception(_i18nService.T("NoSeatsAvailable", organization.Name));
}
}
}
// Create user record - all existing user flows are handled above
var user = new User

View File

@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Logging;
using Stripe;
namespace Bit.Sso
{
@ -34,6 +35,9 @@ namespace Bit.Sso
// Settings
var globalSettings = services.AddGlobalSettingsServices(Configuration);
// Stripe Billing
StripeConfiguration.ApiKey = globalSettings.StripeApiKey;
// Data Protection
services.AddCustomDataProtectionServices(Environment, globalSettings);

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
using Bit.Core.Models.Business;
using Bit.Core.Enums;
@ -15,8 +16,8 @@ namespace Bit.Core.Services
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb, TaxInfo taxInfo);
Task<string> AdjustSeatsAsync(Organization organization, Models.StaticStore.Plan plan, int additionalSeats);
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
Task<string> AdjustSeatsAsync(Organization organization, Models.StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null);
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId, DateTime? prorationDate = null);
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
bool skipInAppPurchaseCheck = false);
Task ReinstateSubscriptionAsync(ISubscriber subscriber);

View File

@ -477,7 +477,7 @@ namespace Bit.Core.Services
}
}
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats);
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats, prorationDate);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization)
{

View File

@ -679,7 +679,7 @@ namespace Bit.Core.Services
}
private async Task<string> FinalizeSubscriptionChangeAsync(IStorableSubscriber storableSubscriber,
SubscriptionUpdate subscriptionUpdate)
SubscriptionUpdate subscriptionUpdate, DateTime? prorationDate)
{
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId);
if (sub == null)
@ -687,7 +687,7 @@ namespace Bit.Core.Services
throw new GatewayException("Subscription not found.");
}
var prorationDate = DateTime.UtcNow;
prorationDate ??= DateTime.UtcNow;
var collectionMethod = sub.CollectionMethod;
var daysUntilDue = sub.DaysUntilDue;
var chargeNow = collectionMethod == "charge_automatically";
@ -793,15 +793,15 @@ namespace Bit.Core.Services
return paymentIntentClientSecret;
}
public Task<string> AdjustSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats)
public Task<string> AdjustSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null)
{
return FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats));
return FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats), prorationDate);
}
public Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
string storagePlanId)
string storagePlanId, DateTime? prorationDate = null)
{
return FinalizeSubscriptionChangeAsync(storableSubscriber, new StorageSubscriptionUpdate(storagePlanId, additionalStorage));
return FinalizeSubscriptionChangeAsync(storableSubscriber, new StorageSubscriptionUpdate(storagePlanId, additionalStorage), prorationDate);
}
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)