1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-16 01:51:21 +01:00

[AC-358] Server changes for self host subscription page changes (#2826)

* [AC-358] Add constant for grace period length

* [AC-358] Add SubscriptionExpiration to OrganizationLicense.cs and increment Current_License_File_Version

* [AC-358] Update org subscription response model

- Add new SelfHostSubscriptionExpiration field that does not include a grace period
- Add optional License argument to constructor for self host responses
- Use the License, if available, to populate the expiration/subscription expiration fields
- Maintain backwards compatability by falling back to organization expiration date

* [AC-358] Read organization license file for self hosted subscription response

* [AC-358] Decrement current license file version and add comment documenting why

* [AC-358] Clarify name for new expiration without grace period field
This commit is contained in:
Shane Melton 2023-05-15 07:38:41 -07:00 committed by GitHub
parent 8d3fe12170
commit bfd3f85bb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 7 deletions

View File

@ -50,6 +50,7 @@ public class OrganizationsController : Controller
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly ILicensingService _licensingService;
public OrganizationsController( public OrganizationsController(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -69,7 +70,8 @@ public class OrganizationsController : Controller
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand, IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IFeatureService featureService, IFeatureService featureService,
GlobalSettings globalSettings) GlobalSettings globalSettings,
ILicensingService licensingService)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -89,6 +91,7 @@ public class OrganizationsController : Controller
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_featureService = featureService; _featureService = featureService;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_licensingService = licensingService;
} }
[HttpGet("{id}")] [HttpGet("{id}")]
@ -156,10 +159,14 @@ public class OrganizationsController : Controller
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData); return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData);
} }
else
if (_globalSettings.SelfHosted)
{ {
return new OrganizationSubscriptionResponseModel(organization); var orgLicense = await _licensingService.ReadOrganizationLicenseAsync(organization);
return new OrganizationSubscriptionResponseModel(organization, orgLicense);
} }
return new OrganizationSubscriptionResponseModel(organization);
} }
[HttpGet("{id}/license")] [HttpGet("{id}/license")]

View File

@ -3,6 +3,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Constants = Bit.Core.Constants;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.Models.Response.Organizations;
@ -108,9 +109,32 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
} }
} }
public OrganizationSubscriptionResponseModel(Organization organization, OrganizationLicense license) :
this(organization)
{
if (license != null)
{
// License expiration should always include grace period - See OrganizationLicense.cs
Expiration = license.Expires;
// Use license.ExpirationWithoutGracePeriod if available, otherwise assume license expiration minus grace period
ExpirationWithoutGracePeriod = license.ExpirationWithoutGracePeriod ??
license.Expires?.AddDays(-Constants
.OrganizationSelfHostSubscriptionGracePeriodDays);
}
}
public string StorageName { get; set; } public string StorageName { get; set; }
public double? StorageGb { get; set; } public double? StorageGb { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }
/// <summary>
/// Date when a self-hosted organization's subscription expires, without any grace period.
/// </summary>
public DateTime? ExpirationWithoutGracePeriod { get; set; }
/// <summary>
/// Date when a self-hosted organization expires (includes grace period).
/// </summary>
public DateTime? Expiration { get; set; } public DateTime? Expiration { get; set; }
} }

View File

@ -13,6 +13,12 @@ public static class Constants
public const long FileSize501mb = 501L * 1024L * 1024L; public const long FileSize501mb = 501L * 1024L * 1024L;
public const string DatabaseFieldProtectorPurpose = "DatabaseFieldProtection"; public const string DatabaseFieldProtectorPurpose = "DatabaseFieldProtection";
public const string DatabaseFieldProtectedPrefix = "P|"; public const string DatabaseFieldProtectedPrefix = "P|";
/// <summary>
/// Default number of days an organization has to apply an updated license to their self-hosted installation after
/// their subscription has expired.
/// </summary>
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;
} }
public static class TokenPurposes public static class TokenPurposes

View File

@ -78,7 +78,8 @@ public class OrganizationLicense : ILicense
subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180)) subscriptionInfo.Subscription.PeriodDuration > TimeSpan.FromDays(180))
{ {
Refresh = DateTime.UtcNow.AddDays(30); Refresh = DateTime.UtcNow.AddDays(30);
Expires = subscriptionInfo?.Subscription.PeriodEndDate.Value.AddDays(60); Expires = subscriptionInfo.Subscription.PeriodEndDate?.AddDays(Constants.OrganizationSelfHostSubscriptionGracePeriodDays);
ExpirationWithoutGracePeriod = subscriptionInfo.Subscription.PeriodEndDate;
} }
else else
{ {
@ -123,6 +124,7 @@ public class OrganizationLicense : ILicense
public DateTime Issued { get; set; } public DateTime Issued { get; set; }
public DateTime? Refresh { get; set; } public DateTime? Refresh { get; set; }
public DateTime? Expires { get; set; } public DateTime? Expires { get; set; }
public DateTime? ExpirationWithoutGracePeriod { get; set; }
public bool Trial { get; set; } public bool Trial { get; set; }
public LicenseType? LicenseType { get; set; } public LicenseType? LicenseType { get; set; }
public string Hash { get; set; } public string Hash { get; set; }
@ -133,10 +135,12 @@ public class OrganizationLicense : ILicense
/// <summary> /// <summary>
/// Represents the current version of the license format. Should be updated whenever new fields are added. /// Represents the current version of the license format. Should be updated whenever new fields are added.
/// </summary> /// </summary>
private const int CURRENT_LICENSE_FILE_VERSION = 10; /// <remarks>Intentionally set one version behind to allow self hosted users some time to update before
/// getting out of date license errors</remarks>
private const int CURRENT_LICENSE_FILE_VERSION = 11;
private bool ValidLicenseVersion private bool ValidLicenseVersion
{ {
get => Version is >= 1 and <= 11; get => Version is >= 1 and <= 12;
} }
public byte[] GetDataBytes(bool forHash = false) public byte[] GetDataBytes(bool forHash = false)
@ -170,6 +174,8 @@ public class OrganizationLicense : ILicense
(Version >= 10 || !p.Name.Equals(nameof(UseScim))) && (Version >= 10 || !p.Name.Equals(nameof(UseScim))) &&
// UseCustomPermissions was added in Version 11 // UseCustomPermissions was added in Version 11
(Version >= 11 || !p.Name.Equals(nameof(UseCustomPermissions))) && (Version >= 11 || !p.Name.Equals(nameof(UseCustomPermissions))) &&
// ExpirationWithoutGracePeriod was added in Version 12
(Version >= 12 || !p.Name.Equals(nameof(ExpirationWithoutGracePeriod))) &&
( (
!forHash || !forHash ||
( (

View File

@ -40,6 +40,7 @@ public class OrganizationsControllerTests : IDisposable
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand; private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
private readonly IOrganizationDomainRepository _organizationDomainRepository; private readonly IOrganizationDomainRepository _organizationDomainRepository;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
private readonly ILicensingService _licensingService;
private readonly OrganizationsController _sut; private readonly OrganizationsController _sut;
@ -63,12 +64,13 @@ public class OrganizationsControllerTests : IDisposable
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>(); _createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
_updateOrganizationLicenseCommand = Substitute.For<IUpdateOrganizationLicenseCommand>(); _updateOrganizationLicenseCommand = Substitute.For<IUpdateOrganizationLicenseCommand>();
_featureService = Substitute.For<IFeatureService>(); _featureService = Substitute.For<IFeatureService>();
_licensingService = Substitute.For<ILicensingService>();
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository, _sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _providerRepository, _organizationService, _userService, _paymentService, _currentContext, _policyRepository, _providerRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand, _ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
_createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand, _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand,
_cloudGetOrganizationLicenseQuery, _featureService, _globalSettings); _cloudGetOrganizationLicenseQuery, _featureService, _globalSettings, _licensingService);
} }
public void Dispose() public void Dispose()