2022-06-29 19:46:41 -04:00
using Bit.Api.Models.Request.Organizations ;
2022-05-10 17:12:09 -04:00
using Bit.Api.Models.Response.Organizations ;
using Bit.Core.Context ;
using Bit.Core.Entities ;
using Bit.Core.Enums ;
using Bit.Core.Exceptions ;
using Bit.Core.Models.OrganizationConnectionConfigs ;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces ;
using Bit.Core.Repositories ;
using Bit.Core.Services ;
using Bit.Core.Settings ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
2022-08-29 15:53:48 -04:00
namespace Bit.Api.Controllers
2022-05-10 17:12:09 -04:00
{
2022-08-29 15:53:48 -04:00
[Authorize("Application")]
[Route("organizations/connections")]
public class OrganizationConnectionsController : Controller
2022-05-10 17:12:09 -04:00
{
2022-08-29 15:53:48 -04:00
private readonly ICreateOrganizationConnectionCommand _createOrganizationConnectionCommand ;
private readonly IUpdateOrganizationConnectionCommand _updateOrganizationConnectionCommand ;
private readonly IDeleteOrganizationConnectionCommand _deleteOrganizationConnectionCommand ;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository ;
private readonly ICurrentContext _currentContext ;
private readonly IGlobalSettings _globalSettings ;
private readonly ILicensingService _licensingService ;
public OrganizationConnectionsController (
ICreateOrganizationConnectionCommand createOrganizationConnectionCommand ,
IUpdateOrganizationConnectionCommand updateOrganizationConnectionCommand ,
IDeleteOrganizationConnectionCommand deleteOrganizationConnectionCommand ,
IOrganizationConnectionRepository organizationConnectionRepository ,
ICurrentContext currentContext ,
IGlobalSettings globalSettings ,
ILicensingService licensingService )
{
_createOrganizationConnectionCommand = createOrganizationConnectionCommand ;
_updateOrganizationConnectionCommand = updateOrganizationConnectionCommand ;
_deleteOrganizationConnectionCommand = deleteOrganizationConnectionCommand ;
_organizationConnectionRepository = organizationConnectionRepository ;
_currentContext = currentContext ;
_globalSettings = globalSettings ;
_licensingService = licensingService ;
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
[HttpGet("enabled")]
public bool ConnectionsEnabled ( )
2022-05-10 17:12:09 -04:00
{
2022-08-29 15:53:48 -04:00
return _globalSettings . SelfHosted & & _globalSettings . EnableCloudCommunication ;
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
[HttpPost]
public async Task < OrganizationConnectionResponseModel > CreateConnection ( [ FromBody ] OrganizationConnectionRequestModel model )
{
if ( ! await HasPermissionAsync ( model ? . OrganizationId ) )
{
throw new BadRequestException ( $"You do not have permission to create a connection of type {model.Type}." ) ;
}
if ( await HasConnectionTypeAsync ( model , null , model . Type ) )
{
throw new BadRequestException ( $"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization." ) ;
}
switch ( model . Type )
{
case OrganizationConnectionType . CloudBillingSync :
return await CreateOrUpdateOrganizationConnectionAsync < BillingSyncConfig > ( null , model , ValidateBillingSyncConfig ) ;
case OrganizationConnectionType . Scim :
return await CreateOrUpdateOrganizationConnectionAsync < ScimConfig > ( null , model ) ;
default :
throw new BadRequestException ( $"Unknown Organization connection Type: {model.Type}" ) ;
}
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
[HttpPut("{organizationConnectionId}")]
public async Task < OrganizationConnectionResponseModel > UpdateConnection ( Guid organizationConnectionId , [ FromBody ] OrganizationConnectionRequestModel model )
{
var existingOrganizationConnection = await _organizationConnectionRepository . GetByIdAsync ( organizationConnectionId ) ;
if ( existingOrganizationConnection = = null )
{
throw new NotFoundException ( ) ;
}
if ( ! await HasPermissionAsync ( model ? . OrganizationId , model ? . Type ) )
{
throw new BadRequestException ( "You do not have permission to update this connection." ) ;
}
if ( await HasConnectionTypeAsync ( model , organizationConnectionId , model . Type ) )
{
throw new BadRequestException ( $"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization." ) ;
}
switch ( model . Type )
{
case OrganizationConnectionType . CloudBillingSync :
return await CreateOrUpdateOrganizationConnectionAsync < BillingSyncConfig > ( organizationConnectionId , model ) ;
case OrganizationConnectionType . Scim :
return await CreateOrUpdateOrganizationConnectionAsync < ScimConfig > ( organizationConnectionId , model ) ;
default :
throw new BadRequestException ( $"Unkown Organization connection Type: {model.Type}" ) ;
}
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
[HttpGet("{organizationId}/{type}")]
public async Task < OrganizationConnectionResponseModel > GetConnection ( Guid organizationId , OrganizationConnectionType type )
{
if ( ! await HasPermissionAsync ( organizationId , type ) )
{
throw new BadRequestException ( $"You do not have permission to retrieve a connection of type {type}." ) ;
}
var connections = await GetConnectionsAsync ( organizationId , type ) ;
var connection = connections . FirstOrDefault ( c = > c . Type = = type ) ;
switch ( type )
{
case OrganizationConnectionType . CloudBillingSync :
if ( ! _globalSettings . SelfHosted )
{
throw new BadRequestException ( $"Cannot get a {type} connection outside of a self-hosted instance." ) ;
}
return new OrganizationConnectionResponseModel ( connection , typeof ( BillingSyncConfig ) ) ;
case OrganizationConnectionType . Scim :
return new OrganizationConnectionResponseModel ( connection , typeof ( ScimConfig ) ) ;
default :
throw new BadRequestException ( $"Unkown Organization connection Type: {type}" ) ;
}
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
[HttpDelete("{organizationConnectionId}")]
[HttpPost("{organizationConnectionId}/delete")]
public async Task DeleteConnection ( Guid organizationConnectionId )
2022-05-10 17:12:09 -04:00
{
2022-08-29 15:53:48 -04:00
var connection = await _organizationConnectionRepository . GetByIdAsync ( organizationConnectionId ) ;
2022-05-10 17:12:09 -04:00
2022-08-29 15:53:48 -04:00
if ( connection = = null )
{
throw new NotFoundException ( ) ;
}
2022-05-10 17:12:09 -04:00
2022-08-29 15:53:48 -04:00
if ( ! await HasPermissionAsync ( connection . OrganizationId , connection . Type ) )
{
throw new BadRequestException ( $"You do not have permission to remove this connection of type {connection.Type}." ) ;
}
2022-05-10 17:12:09 -04:00
2022-08-29 15:53:48 -04:00
await _deleteOrganizationConnectionCommand . DeleteAsync ( connection ) ;
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
private async Task < ICollection < OrganizationConnection > > GetConnectionsAsync ( Guid organizationId , OrganizationConnectionType type ) = >
await _organizationConnectionRepository . GetByOrganizationIdTypeAsync ( organizationId , type ) ;
2022-05-10 17:12:09 -04:00
2022-08-29 15:53:48 -04:00
private async Task < bool > HasConnectionTypeAsync ( OrganizationConnectionRequestModel model , Guid ? connectionId ,
OrganizationConnectionType type )
2022-05-10 17:12:09 -04:00
{
2022-08-29 15:53:48 -04:00
var existingConnections = await GetConnectionsAsync ( model . OrganizationId , type ) ;
2022-05-10 17:12:09 -04:00
2022-08-29 15:53:48 -04:00
return existingConnections . Any ( c = > c . Type = = model . Type & & ( ! connectionId . HasValue | | c . Id ! = connectionId . Value ) ) ;
2022-05-10 17:12:09 -04:00
}
2022-08-29 15:53:48 -04:00
private async Task < bool > HasPermissionAsync ( Guid ? organizationId , OrganizationConnectionType ? type = null )
{
if ( ! organizationId . HasValue )
{
return false ;
}
return type switch
{
OrganizationConnectionType . Scim = > await _currentContext . ManageScim ( organizationId . Value ) ,
_ = > await _currentContext . OrganizationOwner ( organizationId . Value ) ,
} ;
2022-07-14 15:58:48 -04:00
}
2022-08-29 15:53:48 -04:00
private async Task ValidateBillingSyncConfig ( OrganizationConnectionRequestModel < BillingSyncConfig > typedModel )
{
if ( ! _globalSettings . SelfHosted )
{
throw new BadRequestException ( $"Cannot create a {typedModel.Type} connection outside of a self-hosted instance." ) ;
}
var license = await _licensingService . ReadOrganizationLicenseAsync ( typedModel . OrganizationId ) ;
if ( ! _licensingService . VerifyLicense ( license ) )
{
throw new BadRequestException ( "Cannot verify license file." ) ;
}
typedModel . ParsedConfig . CloudOrganizationId = license . Id ;
2022-07-14 15:58:48 -04:00
}
2022-08-29 15:53:48 -04:00
private async Task < OrganizationConnectionResponseModel > CreateOrUpdateOrganizationConnectionAsync < T > (
Guid ? organizationConnectionId ,
OrganizationConnectionRequestModel model ,
Func < OrganizationConnectionRequestModel < T > , Task > validateAction = null )
where T : new ( )
2022-07-14 15:58:48 -04:00
{
2022-08-29 15:53:48 -04:00
var typedModel = new OrganizationConnectionRequestModel < T > ( model ) ;
if ( validateAction ! = null )
{
await validateAction ( typedModel ) ;
}
2022-07-14 15:58:48 -04:00
2022-08-29 15:53:48 -04:00
var data = typedModel . ToData ( organizationConnectionId ) ;
var connection = organizationConnectionId . HasValue
? await _updateOrganizationConnectionCommand . UpdateAsync ( data )
: await _createOrganizationConnectionCommand . CreateAsync ( data ) ;
2022-07-14 15:58:48 -04:00
2022-08-29 15:53:48 -04:00
return new OrganizationConnectionResponseModel ( connection , typeof ( T ) ) ;
}
2022-05-10 17:12:09 -04:00
}
}