2025-07-08 17:32:49 -04:00
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using System.Net ;
2024-03-05 10:56:48 +00:00
using Bit.Admin.AdminConsole.Models ;
2024-02-21 09:18:09 +10:00
using Bit.Admin.Enums ;
2023-05-04 15:18:49 -04:00
using Bit.Admin.Services ;
using Bit.Admin.Utilities ;
2023-10-20 06:37:46 +10:00
using Bit.Core.AdminConsole.Entities ;
2024-11-12 14:11:10 -05:00
using Bit.Core.AdminConsole.Enums.Provider ;
2025-01-27 10:58:08 +00:00
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces ;
2025-08-14 15:02:00 +01:00
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers ;
2024-01-12 10:38:47 -05:00
using Bit.Core.AdminConsole.Providers.Interfaces ;
2023-10-20 06:37:46 +10:00
using Bit.Core.AdminConsole.Repositories ;
2024-11-12 14:11:10 -05:00
using Bit.Core.Billing.Enums ;
2024-04-19 10:09:18 -04:00
using Bit.Core.Billing.Extensions ;
2025-12-16 07:59:05 -06:00
using Bit.Core.Billing.Organizations.Services ;
2025-02-27 07:55:46 -05:00
using Bit.Core.Billing.Pricing ;
2025-05-21 09:04:30 -04:00
using Bit.Core.Billing.Providers.Services ;
2025-12-12 15:32:43 -06:00
using Bit.Core.Billing.Services ;
2018-03-21 17:41:14 -04:00
using Bit.Core.Enums ;
2022-05-10 17:12:09 -04:00
using Bit.Core.Models.OrganizationConnectionConfigs ;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces ;
2018-03-23 09:44:48 -04:00
using Bit.Core.Repositories ;
2023-08-05 07:51:12 +10:00
using Bit.Core.SecretsManager.Repositories ;
2019-02-25 10:39:04 -05:00
using Bit.Core.Services ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2021-08-10 14:38:58 -04:00
using Bit.Core.Utilities ;
2023-03-02 13:23:38 -05:00
using Bit.Core.Vault.Repositories ;
2021-08-10 14:38:58 -04:00
using Microsoft.AspNetCore.Authorization ;
2018-03-21 17:41:14 -04:00
using Microsoft.AspNetCore.Mvc ;
2024-02-21 09:18:09 +10:00
namespace Bit.Admin.AdminConsole.Controllers ;
2022-08-29 16:06:55 -04:00
2018-03-21 17:41:14 -04:00
[Authorize]
public class OrganizationsController : Controller
{
private readonly IOrganizationRepository _organizationRepository ;
2018-03-22 21:10:10 -04:00
private readonly IOrganizationUserRepository _organizationUserRepository ;
2022-05-10 17:12:09 -04:00
private readonly IOrganizationConnectionRepository _organizationConnectionRepository ;
2018-03-21 17:41:14 -04:00
private readonly ISelfHostedSyncSponsorshipsCommand _syncSponsorshipsCommand ;
2020-02-25 14:28:41 -05:00
private readonly ICipherRepository _cipherRepository ;
private readonly ICollectionRepository _collectionRepository ;
private readonly IGroupRepository _groupRepository ;
private readonly IPolicyRepository _policyRepository ;
2025-12-12 15:32:43 -06:00
private readonly IStripePaymentService _paymentService ;
2019-06-13 00:31:50 -04:00
private readonly IApplicationCacheService _applicationCacheService ;
2022-05-16 09:57:00 -04:00
private readonly GlobalSettings _globalSettings ;
2023-04-14 11:13:16 +01:00
private readonly IProviderRepository _providerRepository ;
2022-05-16 09:57:00 -04:00
private readonly ILogger < OrganizationsController > _logger ;
2023-05-04 15:18:49 -04:00
private readonly IAccessControlService _accessControlService ;
2023-08-05 07:51:12 +10:00
private readonly ISecretRepository _secretRepository ;
private readonly IProjectRepository _projectRepository ;
private readonly IServiceAccountRepository _serviceAccountRepository ;
2024-01-12 10:38:47 -05:00
private readonly IProviderOrganizationRepository _providerOrganizationRepository ;
private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand ;
2024-05-23 10:17:00 -04:00
private readonly IProviderBillingService _providerBillingService ;
2025-01-27 10:58:08 +00:00
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand ;
2025-02-27 07:55:46 -05:00
private readonly IPricingClient _pricingClient ;
2025-08-14 15:02:00 +01:00
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand ;
2025-12-16 07:59:05 -06:00
private readonly IOrganizationBillingService _organizationBillingService ;
2022-08-29 16:06:55 -04:00
2022-05-16 09:57:00 -04:00
public OrganizationsController (
2018-03-22 17:33:22 -04:00
IOrganizationRepository organizationRepository ,
2018-03-22 21:10:10 -04:00
IOrganizationUserRepository organizationUserRepository ,
2022-05-10 17:12:09 -04:00
IOrganizationConnectionRepository organizationConnectionRepository ,
ISelfHostedSyncSponsorshipsCommand syncSponsorshipsCommand ,
2022-05-16 09:57:00 -04:00
ICipherRepository cipherRepository ,
2020-02-25 14:28:41 -05:00
ICollectionRepository collectionRepository ,
2018-03-21 17:41:14 -04:00
IGroupRepository groupRepository ,
2020-02-25 14:28:41 -05:00
IPolicyRepository policyRepository ,
2025-12-12 15:32:43 -06:00
IStripePaymentService paymentService ,
2019-06-13 00:31:50 -04:00
IApplicationCacheService applicationCacheService ,
2021-08-10 14:38:58 -04:00
GlobalSettings globalSettings ,
2023-04-14 11:13:16 +01:00
IProviderRepository providerRepository ,
2023-05-04 15:18:49 -04:00
ILogger < OrganizationsController > logger ,
2023-05-16 16:21:57 +02:00
IAccessControlService accessControlService ,
2023-08-05 07:51:12 +10:00
ISecretRepository secretRepository ,
IProjectRepository projectRepository ,
2024-01-12 10:38:47 -05:00
IServiceAccountRepository serviceAccountRepository ,
IProviderOrganizationRepository providerOrganizationRepository ,
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand ,
2024-11-15 09:30:03 -05:00
IProviderBillingService providerBillingService ,
2025-02-27 07:55:46 -05:00
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand ,
2025-08-14 15:02:00 +01:00
IPricingClient pricingClient ,
2025-12-16 07:59:05 -06:00
IResendOrganizationInviteCommand resendOrganizationInviteCommand ,
IOrganizationBillingService organizationBillingService )
2022-08-29 16:06:55 -04:00
{
2018-03-21 17:41:14 -04:00
_organizationRepository = organizationRepository ;
2018-03-22 21:10:10 -04:00
_organizationUserRepository = organizationUserRepository ;
2022-05-10 17:12:09 -04:00
_organizationConnectionRepository = organizationConnectionRepository ;
_syncSponsorshipsCommand = syncSponsorshipsCommand ;
2022-05-16 09:57:00 -04:00
_cipherRepository = cipherRepository ;
2018-03-21 17:41:14 -04:00
_collectionRepository = collectionRepository ;
_groupRepository = groupRepository ;
2020-02-25 14:28:41 -05:00
_policyRepository = policyRepository ;
2018-03-21 17:41:14 -04:00
_paymentService = paymentService ;
2019-06-13 00:31:50 -04:00
_applicationCacheService = applicationCacheService ;
2018-03-22 17:33:22 -04:00
_globalSettings = globalSettings ;
2023-04-14 11:13:16 +01:00
_providerRepository = providerRepository ;
2022-05-16 09:57:00 -04:00
_logger = logger ;
2023-05-04 15:18:49 -04:00
_accessControlService = accessControlService ;
2023-08-05 07:51:12 +10:00
_secretRepository = secretRepository ;
_projectRepository = projectRepository ;
_serviceAccountRepository = serviceAccountRepository ;
2024-01-12 10:38:47 -05:00
_providerOrganizationRepository = providerOrganizationRepository ;
_removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand ;
2024-05-23 10:17:00 -04:00
_providerBillingService = providerBillingService ;
2025-01-27 10:58:08 +00:00
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand ;
2025-02-27 07:55:46 -05:00
_pricingClient = pricingClient ;
2025-08-14 15:02:00 +01:00
_resendOrganizationInviteCommand = resendOrganizationInviteCommand ;
2025-12-16 07:59:05 -06:00
_organizationBillingService = organizationBillingService ;
2022-08-29 16:06:55 -04:00
}
2023-05-04 15:18:49 -04:00
[RequirePermission(Permission.Org_List_View)]
2022-05-16 09:57:00 -04:00
public async Task < IActionResult > Index ( string name = null , string userEmail = null , bool? paid = null ,
2018-03-23 09:29:11 -04:00
int page = 1 , int count = 25 )
2018-03-21 17:41:14 -04:00
{
2019-02-25 10:39:04 -05:00
if ( page < 1 )
2018-03-21 17:41:14 -04:00
{
page = 1 ;
}
2018-03-22 14:29:33 -04:00
2018-03-23 09:29:11 -04:00
if ( count < 1 )
{
2022-05-10 17:12:09 -04:00
count = 1 ;
2022-08-29 14:53:16 -04:00
}
2018-03-22 14:29:33 -04:00
2024-03-05 10:56:48 +00:00
var encodedName = WebUtility . HtmlEncode ( name ) ;
2020-02-25 14:28:41 -05:00
var skip = ( page - 1 ) * count ;
2024-03-05 10:56:48 +00:00
var organizations = await _organizationRepository . SearchAsync ( encodedName , userEmail , paid , skip , count ) ;
2020-02-25 14:28:41 -05:00
return View ( new OrganizationsModel
2022-08-29 14:53:16 -04:00
{
2022-05-10 17:12:09 -04:00
Items = organizations as List < Organization > ,
Name = string . IsNullOrWhiteSpace ( name ) ? null : name ,
UserEmail = string . IsNullOrWhiteSpace ( userEmail ) ? null : userEmail ,
2020-02-25 14:28:41 -05:00
Paid = paid ,
Page = page ,
Count = count ,
Action = _globalSettings . SelfHosted ? "View" : "Edit" ,
2018-03-29 11:26:19 -04:00
SelfHosted = _globalSettings . SelfHosted
2020-02-25 14:28:41 -05:00
} ) ;
2018-03-22 14:29:33 -04:00
}
public async Task < IActionResult > View ( Guid id )
2022-08-29 16:06:55 -04:00
{
2018-03-22 14:29:33 -04:00
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization = = null )
2022-08-29 16:06:55 -04:00
{
2018-03-22 14:29:33 -04:00
return RedirectToAction ( "Index" ) ;
}
2018-03-22 15:50:56 -04:00
2023-04-14 11:13:16 +01:00
var provider = await _providerRepository . GetByOrganizationIdAsync ( id ) ;
2018-03-22 15:50:56 -04:00
var ciphers = await _cipherRepository . GetManyByOrganizationIdAsync ( id ) ;
var collections = await _collectionRepository . GetManyByOrganizationIdAsync ( id ) ;
IEnumerable < Group > groups = null ;
if ( organization . UseGroups )
{
2019-06-13 00:31:50 -04:00
groups = await _groupRepository . GetManyByOrganizationIdAsync ( id ) ;
2022-08-29 16:06:55 -04:00
}
2018-03-22 14:29:33 -04:00
IEnumerable < Policy > policies = null ;
if ( organization . UsePolicies )
2022-08-29 16:06:55 -04:00
{
2018-03-22 15:50:56 -04:00
policies = await _policyRepository . GetManyByOrganizationIdAsync ( id ) ;
2022-08-29 16:06:55 -04:00
}
2018-03-23 09:29:11 -04:00
var users = await _organizationUserRepository . GetManyDetailsByOrganizationAsync ( id ) ;
2019-06-13 00:31:50 -04:00
var billingSyncConnection = _globalSettings . EnableCloudCommunication ? await _organizationConnectionRepository . GetByOrganizationIdTypeAsync ( id , OrganizationConnectionType . CloudBillingSync ) : null ;
2023-08-05 07:51:12 +10:00
var secrets = organization . UseSecretsManager ? await _secretRepository . GetSecretsCountByOrganizationIdAsync ( id ) : - 1 ;
var projects = organization . UseSecretsManager ? await _projectRepository . GetProjectCountByOrganizationIdAsync ( id ) : - 1 ;
var serviceAccounts = organization . UseSecretsManager ? await _serviceAccountRepository . GetServiceAccountCountByOrganizationIdAsync ( id ) : - 1 ;
var smSeats = organization . UseSecretsManager
? await _organizationUserRepository . GetOccupiedSmSeatCountByOrganizationIdAsync ( organization . Id )
: - 1 ;
return View ( new OrganizationViewModel ( organization , provider , billingSyncConnection , users , ciphers , collections , groups , policies ,
secrets , projects , serviceAccounts , smSeats ) ) ;
2018-03-22 15:50:56 -04:00
}
[SelfHosted(NotSelfHostedOnly = true)]
public async Task < IActionResult > Edit ( Guid id )
2022-08-29 16:06:55 -04:00
{
2019-06-13 00:31:50 -04:00
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization = = null )
2022-08-29 16:06:55 -04:00
{
2019-06-13 00:31:50 -04:00
return RedirectToAction ( "Index" ) ;
2018-03-22 15:50:56 -04:00
}
2022-05-10 17:12:09 -04:00
2023-04-14 11:13:16 +01:00
var provider = await _providerRepository . GetByOrganizationIdAsync ( id ) ;
2022-05-10 17:12:09 -04:00
var ciphers = await _cipherRepository . GetManyByOrganizationIdAsync ( id ) ;
var collections = await _collectionRepository . GetManyByOrganizationIdAsync ( id ) ;
IEnumerable < Group > groups = null ;
if ( organization . UseGroups )
{
2018-03-22 15:50:56 -04:00
groups = await _groupRepository . GetManyByOrganizationIdAsync ( id ) ;
2022-08-29 16:06:55 -04:00
}
2022-05-10 17:12:09 -04:00
IEnumerable < Policy > policies = null ;
if ( organization . UsePolicies )
2022-08-29 16:06:55 -04:00
{
2018-03-22 15:50:56 -04:00
policies = await _policyRepository . GetManyByOrganizationIdAsync ( id ) ;
2022-08-29 14:53:16 -04:00
}
2018-03-22 15:50:56 -04:00
var users = await _organizationUserRepository . GetManyDetailsByOrganizationAsync ( id ) ;
var billingInfo = await _paymentService . GetBillingAsync ( organization ) ;
2024-06-11 13:55:23 -04:00
var billingHistoryInfo = await _paymentService . GetBillingHistoryAsync ( organization ) ;
2022-05-10 17:12:09 -04:00
var billingSyncConnection = _globalSettings . EnableCloudCommunication ? await _organizationConnectionRepository . GetByOrganizationIdTypeAsync ( id , OrganizationConnectionType . CloudBillingSync ) : null ;
2023-08-05 07:51:12 +10:00
var secrets = organization . UseSecretsManager ? await _secretRepository . GetSecretsCountByOrganizationIdAsync ( id ) : - 1 ;
var projects = organization . UseSecretsManager ? await _projectRepository . GetProjectCountByOrganizationIdAsync ( id ) : - 1 ;
var serviceAccounts = organization . UseSecretsManager ? await _serviceAccountRepository . GetServiceAccountCountByOrganizationIdAsync ( id ) : - 1 ;
2024-06-11 13:55:23 -04:00
2023-08-05 07:51:12 +10:00
var smSeats = organization . UseSecretsManager
? await _organizationUserRepository . GetOccupiedSmSeatCountByOrganizationIdAsync ( organization . Id )
: - 1 ;
2024-06-11 13:55:23 -04:00
2025-02-27 07:55:46 -05:00
var plans = await _pricingClient . ListPlans ( ) ;
2024-06-11 13:55:23 -04:00
return View ( new OrganizationEditModel (
organization ,
provider ,
users ,
ciphers ,
collections ,
groups ,
policies ,
billingInfo ,
billingHistoryInfo ,
billingSyncConnection ,
_globalSettings ,
2025-02-27 07:55:46 -05:00
plans ,
2024-06-11 13:55:23 -04:00
secrets ,
projects ,
serviceAccounts ,
smSeats ) ) ;
2022-08-29 16:06:55 -04:00
}
2022-08-29 15:53:48 -04:00
2022-05-10 17:12:09 -04:00
[HttpPost]
[ValidateAntiForgeryToken]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task < IActionResult > Edit ( Guid id , OrganizationEditModel model )
2022-08-29 16:06:55 -04:00
{
2024-11-12 14:11:10 -05:00
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization = = null )
{
TempData [ "Error" ] = "Could not find organization to update." ;
return RedirectToAction ( "Index" ) ;
}
var existingOrganizationData = new Organization
{
Id = organization . Id ,
2025-12-16 07:59:05 -06:00
Name = organization . Name ,
BillingEmail = organization . BillingEmail ,
2024-11-12 14:11:10 -05:00
Status = organization . Status ,
PlanType = organization . PlanType ,
Seats = organization . Seats
} ;
2025-06-10 07:50:09 +01:00
if ( model . PlanType . HasValue )
{
var freePlan = await _pricingClient . GetPlanOrThrow ( model . PlanType . Value ) ;
var isDowngradingToFree = organization . PlanType ! = PlanType . Free & & model . PlanType . Value = = PlanType . Free ;
if ( isDowngradingToFree )
{
if ( model . Seats . HasValue & & model . Seats . Value > freePlan . PasswordManager . MaxSeats )
{
TempData [ "Error" ] = $"Organizations with more than {freePlan.PasswordManager.MaxSeats} seats cannot be downgraded to the Free plan" ;
return RedirectToAction ( "Edit" , new { id } ) ;
}
2023-05-04 15:18:49 -04:00
2025-06-10 07:50:09 +01:00
if ( model . MaxCollections > freePlan . PasswordManager . MaxCollections )
{
TempData [ "Error" ] = $"Organizations with more than {freePlan.PasswordManager.MaxCollections} collections cannot be downgraded to the Free plan. Your organization currently has {organization.MaxCollections} collections." ;
return RedirectToAction ( "Edit" , new { id } ) ;
}
2025-02-27 07:55:46 -05:00
2025-06-10 07:50:09 +01:00
model . MaxStorageGb = null ;
model . ExpirationDate = null ;
model . Enabled = true ;
}
}
UpdateOrganization ( organization , model ) ;
var plan = await _pricingClient . GetPlanOrThrow ( organization . PlanType ) ;
2025-02-27 07:55:46 -05:00
if ( organization . UseSecretsManager & & ! plan . SupportsSecretsManager )
2023-08-16 09:03:17 +10:00
{
2024-10-21 17:57:18 +02:00
TempData [ "Error" ] = "Plan does not support Secrets Manager" ;
return RedirectToAction ( "Edit" , new { id } ) ;
2023-08-16 09:03:17 +10:00
}
2024-11-12 14:11:10 -05:00
await HandlePotentialProviderSeatScalingAsync (
existingOrganizationData ,
model ) ;
2022-05-10 17:12:09 -04:00
await _organizationRepository . ReplaceAsync ( organization ) ;
2024-11-12 14:11:10 -05:00
2019-06-13 00:31:50 -04:00
await _applicationCacheService . UpsertOrganizationAbilityAsync ( organization ) ;
2023-09-25 10:16:19 -04:00
2025-12-16 07:59:05 -06:00
// Sync name/email changes to Stripe
if ( existingOrganizationData . Name ! = organization . Name | | existingOrganizationData . BillingEmail ! = organization . BillingEmail )
{
try
{
await _organizationBillingService . UpdateOrganizationNameAndEmail ( organization ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex ,
"Failed to update Stripe customer for organization {OrganizationId}. Database was updated successfully." ,
organization . Id ) ;
TempData [ "Warning" ] = "Organization updated successfully, but Stripe customer name/email synchronization failed." ;
}
}
2022-05-16 09:57:00 -04:00
return RedirectToAction ( "Edit" , new { id } ) ;
2022-05-10 17:12:09 -04:00
}
[HttpPost]
[ValidateAntiForgeryToken]
2023-05-04 15:18:49 -04:00
[RequirePermission(Permission.Org_Delete)]
2022-05-10 17:12:09 -04:00
public async Task < IActionResult > Delete ( Guid id )
2022-08-29 16:06:55 -04:00
{
2022-05-10 17:12:09 -04:00
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
2024-04-19 10:09:18 -04:00
if ( organization = = null )
2022-08-29 16:06:55 -04:00
{
2024-04-19 10:09:18 -04:00
return RedirectToAction ( "Index" ) ;
2022-08-29 15:53:48 -04:00
}
2024-11-15 09:30:03 -05:00
if ( organization . IsValidClient ( ) )
2024-04-19 10:09:18 -04:00
{
var provider = await _providerRepository . GetByOrganizationIdAsync ( organization . Id ) ;
if ( provider . IsBillable ( ) )
{
2024-05-23 10:17:00 -04:00
await _providerBillingService . ScaleSeats (
2024-04-19 10:09:18 -04:00
provider ,
organization . PlanType ,
- organization . Seats ? ? 0 ) ;
}
}
await _organizationRepository . DeleteAsync ( organization ) ;
await _applicationCacheService . DeleteOrganizationAbilityAsync ( organization . Id ) ;
2022-05-10 17:12:09 -04:00
return RedirectToAction ( "Index" ) ;
2022-08-29 16:06:55 -04:00
}
2024-05-22 12:59:19 -04:00
[HttpPost]
[ValidateAntiForgeryToken]
2025-01-29 16:13:36 +00:00
[RequirePermission(Permission.Org_RequestDelete)]
2024-05-22 12:59:19 -04:00
public async Task < IActionResult > DeleteInitiation ( Guid id , OrganizationInitiateDeleteModel model )
{
if ( ! ModelState . IsValid )
{
TempData [ "Error" ] = ModelState . GetErrorMessage ( ) ;
}
else
{
try
{
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization ! = null )
{
2025-01-27 10:58:08 +00:00
await _organizationInitiateDeleteCommand . InitiateDeleteAsync ( organization , model . AdminEmail ) ;
2024-05-22 12:59:19 -04:00
TempData [ "Success" ] = "The request to initiate deletion of the organization has been sent." ;
}
}
catch ( Exception ex )
{
TempData [ "Error" ] = ex . Message ;
}
}
return RedirectToAction ( "Edit" , new { id } ) ;
}
2022-05-10 17:12:09 -04:00
public async Task < IActionResult > TriggerBillingSync ( Guid id )
2022-08-29 16:06:55 -04:00
{
2022-05-10 17:12:09 -04:00
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization = = null )
2022-08-29 15:53:48 -04:00
{
2022-05-10 17:12:09 -04:00
return RedirectToAction ( "Index" ) ;
2022-08-29 16:06:55 -04:00
}
2022-05-10 17:12:09 -04:00
var connection = ( await _organizationConnectionRepository . GetEnabledByOrganizationIdTypeAsync ( id , OrganizationConnectionType . CloudBillingSync ) ) . FirstOrDefault ( ) ;
if ( connection ! = null )
2022-08-29 16:06:55 -04:00
{
try
2022-08-29 14:53:16 -04:00
{
2022-05-10 17:12:09 -04:00
var config = connection . GetConfig < BillingSyncConfig > ( ) ;
await _syncSponsorshipsCommand . SyncOrganization ( id , config . CloudOrganizationId , connection ) ;
TempData [ "ConnectionActivated" ] = id ;
TempData [ "ConnectionError" ] = null ;
2022-08-29 14:53:16 -04:00
}
2022-05-10 17:12:09 -04:00
catch ( Exception ex )
2022-08-29 14:53:16 -04:00
{
2022-05-10 17:12:09 -04:00
TempData [ "ConnectionError" ] = ex . Message ;
2022-05-16 09:57:00 -04:00
_logger . LogWarning ( ex , "Error while attempting to do billing sync for organization with id '{OrganizationId}'" , id ) ;
2022-08-29 14:53:16 -04:00
}
2018-03-29 11:26:19 -04:00
if ( _globalSettings . SelfHosted )
2022-08-29 16:06:55 -04:00
{
2022-05-10 17:12:09 -04:00
return RedirectToAction ( "View" , new { id } ) ;
2022-08-29 16:06:55 -04:00
}
else
{
2022-05-10 17:12:09 -04:00
return RedirectToAction ( "Edit" , new { id } ) ;
2022-08-29 16:06:55 -04:00
}
}
2022-05-10 17:12:09 -04:00
return RedirectToAction ( "Index" ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
2023-04-14 11:13:16 +01:00
[HttpPost]
public async Task < IActionResult > ResendOwnerInvite ( Guid id )
{
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization = = null )
{
return RedirectToAction ( "Index" ) ;
}
var organizationUsers = await _organizationUserRepository . GetManyByOrganizationAsync ( id , OrganizationUserType . Owner ) ;
foreach ( var organizationUser in organizationUsers )
{
2025-08-14 15:02:00 +01:00
await _resendOrganizationInviteCommand . ResendInviteAsync ( id , null , organizationUser . Id , true ) ;
2023-04-14 11:13:16 +01:00
}
return Json ( null ) ;
}
2024-01-12 10:38:47 -05:00
[HttpPost]
[RequirePermission(Permission.Provider_Edit)]
public async Task < IActionResult > UnlinkOrganizationFromProviderAsync ( Guid id )
{
var organization = await _organizationRepository . GetByIdAsync ( id ) ;
if ( organization is null )
{
return RedirectToAction ( "Index" ) ;
}
var provider = await _providerRepository . GetByOrganizationIdAsync ( id ) ;
if ( provider is null )
{
return RedirectToAction ( "Edit" , new { id } ) ;
}
var providerOrganization = await _providerOrganizationRepository . GetByOrganizationId ( id ) ;
if ( providerOrganization is null )
{
return RedirectToAction ( "Edit" , new { id } ) ;
}
await _removeOrganizationFromProviderCommand . RemoveOrganizationFromProvider (
provider ,
providerOrganization ,
organization ) ;
return Json ( null ) ;
}
2023-05-04 15:18:49 -04:00
2024-11-12 14:11:10 -05:00
private void UpdateOrganization ( Organization organization , OrganizationEditModel model )
{
2025-01-31 15:01:26 +00:00
if ( _accessControlService . UserHasPermission ( Permission . Org_Name_Edit ) )
{
organization . Name = WebUtility . HtmlEncode ( model . Name ) ;
}
2023-05-04 15:18:49 -04:00
if ( _accessControlService . UserHasPermission ( Permission . Org_CheckEnabledBox ) )
{
organization . Enabled = model . Enabled ;
}
if ( _accessControlService . UserHasPermission ( Permission . Org_Plan_Edit ) )
{
organization . PlanType = model . PlanType . Value ;
organization . Plan = model . Plan ;
organization . Seats = model . Seats ;
organization . MaxAutoscaleSeats = model . MaxAutoscaleSeats ;
organization . MaxCollections = model . MaxCollections ;
organization . MaxStorageGb = model . MaxStorageGb ;
//features
organization . SelfHost = model . SelfHost ;
organization . Use2fa = model . Use2fa ;
organization . UseApi = model . UseApi ;
organization . UseGroups = model . UseGroups ;
organization . UsePolicies = model . UsePolicies ;
organization . UseSso = model . UseSso ;
organization . UseKeyConnector = model . UseKeyConnector ;
organization . UseScim = model . UseScim ;
organization . UseDirectory = model . UseDirectory ;
organization . UseEvents = model . UseEvents ;
organization . UseResetPassword = model . UseResetPassword ;
organization . UseCustomPermissions = model . UseCustomPermissions ;
organization . UseTotp = model . UseTotp ;
organization . UsersGetPremium = model . UsersGetPremium ;
organization . UseSecretsManager = model . UseSecretsManager ;
2024-12-05 10:46:01 -06:00
organization . UseRiskInsights = model . UseRiskInsights ;
2025-05-15 10:42:51 -04:00
organization . UseOrganizationDomains = model . UseOrganizationDomains ;
2025-04-17 14:58:29 +01:00
organization . UseAdminSponsoredFamilies = model . UseAdminSponsoredFamilies ;
2025-10-20 07:27:18 -05:00
organization . UseAutomaticUserConfirmation = model . UseAutomaticUserConfirmation ;
2025-12-01 12:31:36 -06:00
organization . UsePhishingBlocker = model . UsePhishingBlocker ;
2023-06-27 05:23:23 -04:00
//secrets
organization . SmSeats = model . SmSeats ;
organization . MaxAutoscaleSmSeats = model . MaxAutoscaleSmSeats ;
organization . SmServiceAccounts = model . SmServiceAccounts ;
organization . MaxAutoscaleSmServiceAccounts = model . MaxAutoscaleSmServiceAccounts ;
2023-05-04 15:18:49 -04:00
}
if ( _accessControlService . UserHasPermission ( Permission . Org_Licensing_Edit ) )
{
organization . LicenseKey = model . LicenseKey ;
organization . ExpirationDate = model . ExpirationDate ;
}
if ( _accessControlService . UserHasPermission ( Permission . Org_Billing_Edit ) )
{
organization . BillingEmail = model . BillingEmail ? . ToLowerInvariant ( ) ? . Trim ( ) ;
organization . Gateway = model . Gateway ;
organization . GatewayCustomerId = model . GatewayCustomerId ;
organization . GatewaySubscriptionId = model . GatewaySubscriptionId ;
}
2024-11-12 14:11:10 -05:00
}
private async Task HandlePotentialProviderSeatScalingAsync (
Organization organization ,
OrganizationEditModel update )
{
var provider = await _providerRepository . GetByOrganizationIdAsync ( organization . Id ) ;
// No scaling required
if ( provider is not { Type : ProviderType . Msp , Status : ProviderStatusType . Billable } | |
organization is not { Status : OrganizationStatusType . Managed } | |
! organization . Seats . HasValue | |
update is { Seats : null , PlanType : null } | |
update is { PlanType : not PlanType . TeamsMonthly and not PlanType . EnterpriseMonthly } | |
( PlanTypesMatch ( ) & & SeatsMatch ( ) ) )
{
return ;
}
// Only scale the plan
if ( ! PlanTypesMatch ( ) & & SeatsMatch ( ) )
{
await _providerBillingService . ScaleSeats ( provider , organization . PlanType , - organization . Seats . Value ) ;
await _providerBillingService . ScaleSeats ( provider , update . PlanType ! . Value , organization . Seats . Value ) ;
}
// Only scale the seats
else if ( PlanTypesMatch ( ) & & ! SeatsMatch ( ) )
{
var seatAdjustment = update . Seats ! . Value - organization . Seats . Value ;
await _providerBillingService . ScaleSeats ( provider , organization . PlanType , seatAdjustment ) ;
}
// Scale both
else if ( ! PlanTypesMatch ( ) & & ! SeatsMatch ( ) )
{
var seatAdjustment = update . Seats ! . Value - organization . Seats . Value ;
var planTypeAdjustment = organization . Seats . Value ;
var totalAdjustment = seatAdjustment + planTypeAdjustment ;
await _providerBillingService . ScaleSeats ( provider , organization . PlanType , - organization . Seats . Value ) ;
await _providerBillingService . ScaleSeats ( provider , update . PlanType ! . Value , totalAdjustment ) ;
}
return ;
bool PlanTypesMatch ( )
= > update . PlanType . HasValue & & update . PlanType . Value = = organization . PlanType ;
2023-05-04 15:18:49 -04:00
2024-11-12 14:11:10 -05:00
bool SeatsMatch ( )
= > update . Seats . HasValue & & update . Seats . Value = = organization . Seats ;
2023-05-04 15:18:49 -04:00
}
2018-03-21 17:41:14 -04:00
}