1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

[AC-85] Set Max Seats Autoscale and Current Seats via Public API (#3389)

* Add new public models and controllers

* Resolve pr comments

* Fix the failing test

* Change the controller name

* resolve pr comments

* add the IValidatableObject

* resolve pr comment

* resolve pr comments

* resolve pr comments

* resolve

* removing the whitespaces

* code refactoring
This commit is contained in:
cyprain-okeke 2023-12-21 22:10:14 +01:00 committed by GitHub
parent 3bffd09472
commit cedbea4a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 219 additions and 0 deletions

View File

@ -0,0 +1,81 @@
using System.Net;
using Bit.Api.Models.Public.Response;
using Bit.Core.Context;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OrganizationSubscriptionUpdateRequestModel = Bit.Api.Billing.Public.Models.OrganizationSubscriptionUpdateRequestModel;
namespace Bit.Api.Billing.Public.Controllers;
[Route("public/organization")]
[Authorize("Organization")]
public class OrganizationController : Controller
{
private readonly IOrganizationService _organizationService;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
public OrganizationController(
IOrganizationService organizationService,
ICurrentContext currentContext,
IOrganizationRepository organizationRepository,
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand)
{
_organizationService = organizationService;
_currentContext = currentContext;
_organizationRepository = organizationRepository;
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
}
/// <summary>
/// Update the organization's current subscription for Password Manager and/or Secrets Manager.
/// </summary>
/// <param name="model">The request model containing the updated subscription information.</param>
[HttpPut("subscription")]
[SelfHosted(NotSelfHostedOnly = true)]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> PostSubscriptionAsync([FromBody] OrganizationSubscriptionUpdateRequestModel model)
{
await UpdatePasswordManagerAsync(model, _currentContext.OrganizationId.Value);
await UpdateSecretsManagerAsync(model, _currentContext.OrganizationId.Value);
return new OkResult();
}
private async Task UpdatePasswordManagerAsync(OrganizationSubscriptionUpdateRequestModel model, Guid organizationId)
{
if (model.PasswordManager != null)
{
var organization = await _organizationRepository.GetByIdAsync(organizationId);
model.PasswordManager.ToPasswordManagerSubscriptionUpdate(organization);
await _organizationService.UpdateSubscription(organization.Id, (int)model.PasswordManager.Seats,
model.PasswordManager.MaxAutoScaleSeats);
if (model.PasswordManager.Storage.HasValue)
{
await _organizationService.AdjustStorageAsync(organization.Id, (short)model.PasswordManager.Storage);
}
}
}
private async Task UpdateSecretsManagerAsync(OrganizationSubscriptionUpdateRequestModel model, Guid organizationId)
{
if (model.SecretsManager != null)
{
var organization =
await _organizationRepository.GetByIdAsync(organizationId);
var organizationUpdate = model.SecretsManager.ToSecretsManagerSubscriptionUpdate(organization);
await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(organizationUpdate);
}
}
}

View File

@ -0,0 +1,138 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Business;
namespace Bit.Api.Billing.Public.Models;
public class OrganizationSubscriptionUpdateRequestModel : IValidatableObject
{
public PasswordManagerSubscriptionUpdateModel PasswordManager { get; set; }
public SecretsManagerSubscriptionUpdateModel SecretsManager { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (PasswordManager == null && SecretsManager == null)
{
yield return new ValidationResult("At least one of PasswordManager or SecretsManager must be provided.");
}
yield return ValidationResult.Success;
}
}
public class PasswordManagerSubscriptionUpdateModel
{
public int? Seats { get; set; }
public int? Storage { get; set; }
private int? _maxAutoScaleSeats;
public int? MaxAutoScaleSeats
{
get { return _maxAutoScaleSeats; }
set { _maxAutoScaleSeats = value < 0 ? null : value; }
}
public virtual void ToPasswordManagerSubscriptionUpdate(Organization organization)
{
UpdateMaxAutoScaleSeats(organization);
UpdateSeats(organization);
UpdateStorage(organization);
}
private void UpdateMaxAutoScaleSeats(Organization organization)
{
MaxAutoScaleSeats ??= organization.MaxAutoscaleSeats;
}
private void UpdateSeats(Organization organization)
{
if (Seats is > 0)
{
if (organization.Seats.HasValue)
{
Seats = Seats.Value - organization.Seats.Value;
}
}
else
{
Seats = 0;
}
}
private void UpdateStorage(Organization organization)
{
if (Storage is > 0)
{
if (organization.MaxStorageGb.HasValue)
{
Storage = (short?)(Storage - organization.MaxStorageGb.Value);
}
}
else
{
Storage = null;
}
}
}
public class SecretsManagerSubscriptionUpdateModel
{
public int? Seats { get; set; }
private int? _maxAutoScaleSeats;
public int? MaxAutoScaleSeats
{
get { return _maxAutoScaleSeats; }
set { _maxAutoScaleSeats = value < 0 ? null : value; }
}
public int? ServiceAccounts { get; set; }
private int? _maxAutoScaleServiceAccounts;
public int? MaxAutoScaleServiceAccounts
{
get { return _maxAutoScaleServiceAccounts; }
set { _maxAutoScaleServiceAccounts = value < 0 ? null : value; }
}
public virtual SecretsManagerSubscriptionUpdate ToSecretsManagerSubscriptionUpdate(Organization organization)
{
var update = UpdateUpdateMaxAutoScale(organization);
UpdateSeats(organization, update);
UpdateServiceAccounts(organization, update);
return update;
}
private SecretsManagerSubscriptionUpdate UpdateUpdateMaxAutoScale(Organization organization)
{
var update = new SecretsManagerSubscriptionUpdate(organization, false)
{
MaxAutoscaleSmSeats = MaxAutoScaleSeats ?? organization.MaxAutoscaleSmSeats,
MaxAutoscaleSmServiceAccounts = MaxAutoScaleServiceAccounts ?? organization.MaxAutoscaleSmServiceAccounts
};
return update;
}
private void UpdateSeats(Organization organization, SecretsManagerSubscriptionUpdate update)
{
if (Seats is > 0)
{
if (organization.SmSeats.HasValue)
{
Seats = Seats.Value - organization.SmSeats.Value;
}
update.AdjustSeats(Seats.Value);
}
}
private void UpdateServiceAccounts(Organization organization, SecretsManagerSubscriptionUpdate update)
{
if (ServiceAccounts is > 0)
{
if (organization.SmServiceAccounts.HasValue)
{
ServiceAccounts = ServiceAccounts.Value - organization.SmServiceAccounts.Value;
}
update.AdjustServiceAccounts(ServiceAccounts.Value);
}
}
}