using Bit.Api.Billing.Controllers; using Bit.Api.Billing.Models.Requests; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Billing.Providers.Services; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.AdminConsole.Controllers; [Route("providers/{providerId:guid}/clients")] public class ProviderClientsController( ICurrentContext currentContext, ILogger logger, IOrganizationRepository organizationRepository, IProviderBillingService providerBillingService, IProviderOrganizationRepository providerOrganizationRepository, IProviderRepository providerRepository, IProviderService providerService, IUserService userService) : BaseProviderController(currentContext, logger, providerRepository, userService) { private readonly ICurrentContext _currentContext = currentContext; [HttpPost] [SelfHosted(NotSelfHostedOnly = true)] public async Task CreateAsync( [FromRoute] Guid providerId, [FromBody] CreateClientOrganizationRequestBody requestBody) { var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); if (provider == null) { return result; } var user = await UserService.GetUserByPrincipalAsync(User); if (user == null) { return Error.Unauthorized(); } var organizationSignup = new OrganizationSignup { Name = requestBody.Name, Plan = requestBody.PlanType, AdditionalSeats = requestBody.Seats, Owner = user, BillingEmail = provider.BillingEmail, OwnerKey = requestBody.Key, PublicKey = requestBody.KeyPair.PublicKey, PrivateKey = requestBody.KeyPair.EncryptedPrivateKey, CollectionName = requestBody.CollectionName, IsFromProvider = true }; var providerOrganization = await providerService.CreateOrganizationAsync( providerId, organizationSignup, requestBody.OwnerEmail, user); var clientOrganization = await organizationRepository.GetByIdAsync(providerOrganization.OrganizationId); await providerBillingService.ScaleSeats( provider, requestBody.PlanType, requestBody.Seats); await providerBillingService.CreateCustomerForClientOrganization( provider, clientOrganization); clientOrganization.Status = OrganizationStatusType.Managed; await organizationRepository.ReplaceAsync(clientOrganization); return TypedResults.Ok(); } [HttpPut("{providerOrganizationId:guid}")] [SelfHosted(NotSelfHostedOnly = true)] public async Task UpdateAsync( [FromRoute] Guid providerId, [FromRoute] Guid providerOrganizationId, [FromBody] UpdateClientOrganizationRequestBody requestBody) { var (provider, result) = await TryGetBillableProviderForServiceUserOperation(providerId); if (provider == null) { return result; } var providerOrganization = await providerOrganizationRepository.GetByIdAsync(providerOrganizationId); if (providerOrganization == null) { return Error.NotFound(); } var clientOrganization = await organizationRepository.GetByIdAsync(providerOrganization.OrganizationId); if (clientOrganization is not { Status: OrganizationStatusType.Managed }) { return Error.ServerError(); } var seatAdjustment = requestBody.AssignedSeats - (clientOrganization.Seats ?? 0); var seatAdjustmentResultsInPurchase = await providerBillingService.SeatAdjustmentResultsInPurchase( provider, clientOrganization.PlanType, seatAdjustment); if (seatAdjustmentResultsInPurchase && !_currentContext.ProviderProviderAdmin(provider.Id)) { return Error.Unauthorized("Service users cannot purchase additional seats."); } await providerBillingService.ScaleSeats(provider, clientOrganization.PlanType, seatAdjustment); clientOrganization.Name = requestBody.Name; clientOrganization.Seats = requestBody.AssignedSeats; await organizationRepository.ReplaceAsync(clientOrganization); return TypedResults.Ok(); } [HttpGet("addable")] [SelfHosted(NotSelfHostedOnly = true)] public async Task GetAddableOrganizationsAsync([FromRoute] Guid providerId) { var (provider, result) = await TryGetBillableProviderForServiceUserOperation(providerId); if (provider == null) { return result; } var userId = _currentContext.UserId; if (!userId.HasValue) { return Error.Unauthorized(); } var addable = await providerBillingService.GetAddableOrganizations(provider, userId.Value); return TypedResults.Ok(addable); } [HttpPost("existing")] [SelfHosted(NotSelfHostedOnly = true)] public async Task AddExistingOrganizationAsync( [FromRoute] Guid providerId, [FromBody] AddExistingOrganizationRequestBody requestBody) { var (provider, result) = await TryGetBillableProviderForServiceUserOperation(providerId); if (provider == null) { return result; } var organization = await organizationRepository.GetByIdAsync(requestBody.OrganizationId); if (organization == null) { return Error.BadRequest("The organization being added to the provider does not exist."); } await providerBillingService.AddExistingOrganization(provider, organization, requestBody.Key); return TypedResults.Ok(); } }