1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

[EC-826] Merge license sync feature branch to master (#2587)

* [EC-634] Extract GenerateLicenseAsync to a query (#2373)

* [EC-637] Add license sync to server (#2453)

* [EC-1036] Show correct license sync date (#2626)

* Update method name per new pattern
This commit is contained in:
Thomas Rittson
2023-01-31 07:42:10 +10:00
committed by GitHub
parent d0355fcd12
commit 82908b1fb7
37 changed files with 746 additions and 123 deletions

View File

@ -1,6 +1,9 @@
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.OrganizationLicenses;
using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
@ -14,26 +17,26 @@ namespace Bit.Api.Controllers;
[SelfHosted(NotSelfHostedOnly = true)]
public class LicensesController : Controller
{
private readonly ILicensingService _licensingService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
private readonly ICurrentContext _currentContext;
public LicensesController(
ILicensingService licensingService,
IUserRepository userRepository,
IUserService userService,
IOrganizationRepository organizationRepository,
IOrganizationService organizationService,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICurrentContext currentContext)
{
_licensingService = licensingService;
_userRepository = userRepository;
_userService = userService;
_organizationRepository = organizationRepository;
_organizationService = organizationService;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
_currentContext = currentContext;
}
@ -55,21 +58,30 @@ public class LicensesController : Controller
return license;
}
/// <summary>
/// Used by self-hosted installations to get an updated license file
/// </summary>
[HttpGet("organization/{id}")]
public async Task<OrganizationLicense> GetOrganization(string id, [FromQuery] string key)
public async Task<OrganizationLicense> OrganizationSync(string id, [FromBody] SelfHostedOrganizationLicenseRequestModel model)
{
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
if (org == null)
var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
if (organization == null)
{
return null;
throw new NotFoundException("Organization not found.");
}
else if (!org.LicenseKey.Equals(key))
if (!organization.LicenseKey.Equals(model.LicenseKey))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
}
var license = await _organizationService.GenerateLicenseAsync(org, _currentContext.InstallationId.Value);
if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(organization, model.BillingSyncKey))
{
throw new BadRequestException("Invalid Billing Sync Key");
}
var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(organization, _currentContext.InstallationId.Value);
return license;
}
}

View File

@ -191,7 +191,7 @@ public class OrganizationConnectionsController : Controller
Guid? organizationConnectionId,
OrganizationConnectionRequestModel model,
Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null)
where T : new()
where T : IConnectionConfig
{
var typedModel = new OrganizationConnectionRequestModel<T>(model);
if (validateAction != null)

View File

@ -5,6 +5,7 @@ using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Request.OrganizationSponsorships;
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;

View File

@ -11,6 +11,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@ -37,6 +38,7 @@ public class OrganizationsController : Controller
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly GlobalSettings _globalSettings;
public OrganizationsController(
@ -53,6 +55,7 @@ public class OrganizationsController : Controller
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
GlobalSettings globalSettings)
{
_organizationRepository = organizationRepository;
@ -68,6 +71,7 @@ public class OrganizationsController : Controller
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_globalSettings = globalSettings;
}
@ -149,7 +153,8 @@ public class OrganizationsController : Controller
throw new NotFoundException();
}
var license = await _organizationService.GenerateLicenseAsync(orgIdGuid, installationId);
var org = await _organizationRepository.GetByIdAsync(new Guid(id));
var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(org, installationId);
if (license == null)
{
throw new NotFoundException();
@ -215,6 +220,7 @@ public class OrganizationsController : Controller
return new OrganizationResponseModel(result.Item1);
}
[Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)]
public async Task<OrganizationResponseModel> PostLicense(OrganizationCreateLicenseRequestModel model)
@ -448,6 +454,7 @@ public class OrganizationsController : Controller
}
}
[Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("{id}/license")]
[SelfHosted(SelfHostedOnly = true)]
public async Task PostLicense(string id, LicenseRequestModel model)

View File

@ -0,0 +1,117 @@
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response.Organizations;
using Bit.Api.Utilities;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers.SelfHosted;
[Route("organizations/licenses/self-hosted")]
[Authorize("Application")]
[SelfHosted(SelfHostedOnly = true)]
public class SelfHostedOrganizationLicensesController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly ISelfHostedGetOrganizationLicenseQuery _selfHostedGetOrganizationLicenseQuery;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserService _userService;
public SelfHostedOrganizationLicensesController(
ICurrentContext currentContext,
ISelfHostedGetOrganizationLicenseQuery selfHostedGetOrganizationLicenseQuery,
IOrganizationConnectionRepository organizationConnectionRepository,
IOrganizationService organizationService,
IOrganizationRepository organizationRepository,
IUserService userService)
{
_currentContext = currentContext;
_selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery;
_organizationConnectionRepository = organizationConnectionRepository;
_organizationService = organizationService;
_organizationRepository = organizationRepository;
_userService = userService;
}
[HttpPost("")]
public async Task<OrganizationResponseModel> PostLicenseAsync(OrganizationCreateLicenseRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
if (license == null)
{
throw new BadRequestException("Invalid license");
}
var result = await _organizationService.SignUpAsync(license, user, model.Key,
model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
return new OrganizationResponseModel(result.Item1);
}
[HttpPost("{id}")]
public async Task PostLicenseAsync(string id, LicenseRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
if (license == null)
{
throw new BadRequestException("Invalid license");
}
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
}
[HttpPost("{id}/sync")]
public async Task SyncLicenseAsync(string id)
{
var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationOwner(organization.Id))
{
throw new NotFoundException();
}
var billingSyncConnection =
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
if (billingSyncConnection == null)
{
throw new NotFoundException("Unable to get Cloud Billing Sync connection");
}
var license =
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection);
await _organizationService.UpdateLicenseAsync(organization.Id, license);
var config = billingSyncConnection.GetConfig<BillingSyncConfig>();
config.LastLicenseSync = DateTime.Now;
billingSyncConnection.SetConfig(config);
await _organizationConnectionRepository.ReplaceAsync(billingSyncConnection);
}
}

View File

@ -2,6 +2,7 @@
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations;
@ -17,7 +18,7 @@ public class OrganizationConnectionRequestModel
}
public class OrganizationConnectionRequestModel<T> : OrganizationConnectionRequestModel where T : new()
public class OrganizationConnectionRequestModel<T> : OrganizationConnectionRequestModel where T : IConnectionConfig
{
public T ParsedConfig { get; private set; }