mirror of
https://github.com/bitwarden/server.git
synced 2025-02-10 00:51:22 +01:00
Add autoscale to SSO auto provision (#1642)
This commit is contained in:
parent
86755e130c
commit
18006591fc
@ -25,6 +25,7 @@ using Bit.Core.Models.Api;
|
|||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
|
||||||
namespace Bit.Sso.Controllers
|
namespace Bit.Sso.Controllers
|
||||||
{
|
{
|
||||||
@ -37,6 +38,7 @@ namespace Bit.Sso.Controllers
|
|||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
private readonly ISsoUserRepository _ssoUserRepository;
|
private readonly ISsoUserRepository _ssoUserRepository;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
@ -44,6 +46,7 @@ namespace Bit.Sso.Controllers
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly UserManager<User> _userManager;
|
private readonly UserManager<User> _userManager;
|
||||||
|
private readonly IGlobalSettings _globalSettings;
|
||||||
private readonly Core.Services.IEventService _eventService;
|
private readonly Core.Services.IEventService _eventService;
|
||||||
|
|
||||||
public AccountController(
|
public AccountController(
|
||||||
@ -53,6 +56,7 @@ namespace Bit.Sso.Controllers
|
|||||||
ILogger<AccountController> logger,
|
ILogger<AccountController> logger,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IOrganizationService organizationService,
|
||||||
ISsoConfigRepository ssoConfigRepository,
|
ISsoConfigRepository ssoConfigRepository,
|
||||||
ISsoUserRepository ssoUserRepository,
|
ISsoUserRepository ssoUserRepository,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
@ -60,6 +64,7 @@ namespace Bit.Sso.Controllers
|
|||||||
IUserService userService,
|
IUserService userService,
|
||||||
II18nService i18nService,
|
II18nService i18nService,
|
||||||
UserManager<User> userManager,
|
UserManager<User> userManager,
|
||||||
|
IGlobalSettings globalSettings,
|
||||||
Core.Services.IEventService eventService)
|
Core.Services.IEventService eventService)
|
||||||
{
|
{
|
||||||
_schemeProvider = schemeProvider;
|
_schemeProvider = schemeProvider;
|
||||||
@ -68,6 +73,7 @@ namespace Bit.Sso.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
_organizationService = organizationService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_ssoConfigRepository = ssoConfigRepository;
|
_ssoConfigRepository = ssoConfigRepository;
|
||||||
_ssoUserRepository = ssoUserRepository;
|
_ssoUserRepository = ssoUserRepository;
|
||||||
@ -76,6 +82,7 @@ namespace Bit.Sso.Controllers
|
|||||||
_i18nService = i18nService;
|
_i18nService = i18nService;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -469,12 +476,36 @@ namespace Bit.Sso.Controllers
|
|||||||
if (orgUser == null && organization.Seats.HasValue)
|
if (orgUser == null && organization.Seats.HasValue)
|
||||||
{
|
{
|
||||||
var userCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(orgId);
|
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)
|
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));
|
throw new Exception(_i18nService.T("NoSeatsAvailable", organization.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create user record - all existing user flows are handled above
|
// Create user record - all existing user flows are handled above
|
||||||
var user = new User
|
var user = new User
|
||||||
|
@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.IdentityModel.Logging;
|
using Microsoft.IdentityModel.Logging;
|
||||||
|
using Stripe;
|
||||||
|
|
||||||
namespace Bit.Sso
|
namespace Bit.Sso
|
||||||
{
|
{
|
||||||
@ -34,6 +35,9 @@ namespace Bit.Sso
|
|||||||
// Settings
|
// Settings
|
||||||
var globalSettings = services.AddGlobalSettingsServices(Configuration);
|
var globalSettings = services.AddGlobalSettingsServices(Configuration);
|
||||||
|
|
||||||
|
// Stripe Billing
|
||||||
|
StripeConfiguration.ApiKey = globalSettings.StripeApiKey;
|
||||||
|
|
||||||
// Data Protection
|
// Data Protection
|
||||||
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
services.AddCustomDataProtectionServices(Environment, globalSettings);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -15,8 +16,8 @@ namespace Bit.Core.Services
|
|||||||
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
|
short additionalStorageGb, int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo);
|
||||||
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
short additionalStorageGb, TaxInfo taxInfo);
|
short additionalStorageGb, TaxInfo taxInfo);
|
||||||
Task<string> AdjustSeatsAsync(Organization organization, Models.StaticStore.Plan plan, int additionalSeats);
|
Task<string> AdjustSeatsAsync(Organization organization, Models.StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null);
|
||||||
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
|
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId, DateTime? prorationDate = null);
|
||||||
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
|
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false,
|
||||||
bool skipInAppPurchaseCheck = false);
|
bool skipInAppPurchaseCheck = false);
|
||||||
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
||||||
|
@ -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(
|
await _referenceEventService.RaiseEventAsync(
|
||||||
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization)
|
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization)
|
||||||
{
|
{
|
||||||
|
@ -679,7 +679,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> FinalizeSubscriptionChangeAsync(IStorableSubscriber storableSubscriber,
|
private async Task<string> FinalizeSubscriptionChangeAsync(IStorableSubscriber storableSubscriber,
|
||||||
SubscriptionUpdate subscriptionUpdate)
|
SubscriptionUpdate subscriptionUpdate, DateTime? prorationDate)
|
||||||
{
|
{
|
||||||
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId);
|
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId);
|
||||||
if (sub == null)
|
if (sub == null)
|
||||||
@ -687,7 +687,7 @@ namespace Bit.Core.Services
|
|||||||
throw new GatewayException("Subscription not found.");
|
throw new GatewayException("Subscription not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var prorationDate = DateTime.UtcNow;
|
prorationDate ??= DateTime.UtcNow;
|
||||||
var collectionMethod = sub.CollectionMethod;
|
var collectionMethod = sub.CollectionMethod;
|
||||||
var daysUntilDue = sub.DaysUntilDue;
|
var daysUntilDue = sub.DaysUntilDue;
|
||||||
var chargeNow = collectionMethod == "charge_automatically";
|
var chargeNow = collectionMethod == "charge_automatically";
|
||||||
@ -793,15 +793,15 @@ namespace Bit.Core.Services
|
|||||||
return paymentIntentClientSecret;
|
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,
|
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)
|
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
|
||||||
|
Loading…
Reference in New Issue
Block a user