diff --git a/src/Core/Billing/Licenses/LicenseConstants.cs b/src/Core/Billing/Licenses/LicenseConstants.cs index 50510914a5..513578f43e 100644 --- a/src/Core/Billing/Licenses/LicenseConstants.cs +++ b/src/Core/Billing/Licenses/LicenseConstants.cs @@ -41,6 +41,7 @@ public static class OrganizationLicenseConstants public const string Refresh = nameof(Refresh); public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod); public const string Trial = nameof(Trial); + public const string UseAdminSponsoredFamilies = nameof(UseAdminSponsoredFamilies); } public static class UserLicenseConstants diff --git a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs index 62e1889564..6819d3cc0b 100644 --- a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs +++ b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs @@ -53,6 +53,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory + /// Initializes a new instance of the class. + /// + /// + /// + /// ⚠️ DEPRECATED: This constructor and the entire property-based licensing system is deprecated. + /// Do not add new properties to this constructor or extend its functionality. + /// + /// + /// This implementation has been replaced by a new claims-based licensing system that provides better security + /// and flexibility. The new system uses JWT claims to store and validate license information, making it more + /// secure and easier to extend without requiring changes to the license format. + /// + /// + /// For new license-related features or modifications: + /// 1. Use the claims-based system instead of adding properties here + /// 2. Add new claims to the license token + /// 3. Validate claims in the and methods + /// + /// + /// This constructor is maintained only for backward compatibility with existing licenses. + /// + /// + /// The organization to create the license for. + /// Information about the organization's subscription. + /// The ID of the current installation. + /// The service used to sign the license. + /// Optional version number for the license format. public OrganizationLicense(Organization org, SubscriptionInfo subscriptionInfo, Guid installationId, ILicensingService licenseService, int? version = null) { @@ -105,6 +133,7 @@ public class OrganizationLicense : ILicense Trial = false; } + UseAdminSponsoredFamilies = org.UseAdminSponsoredFamilies; Hash = Convert.ToBase64String(ComputeHash()); Signature = Convert.ToBase64String(licenseService.SignLicense(this)); } @@ -153,6 +182,7 @@ public class OrganizationLicense : ILicense public bool Trial { get; set; } public LicenseType? LicenseType { get; set; } + public bool UseAdminSponsoredFamilies { get; set; } public string Hash { get; set; } public string Signature { get; set; } public string Token { get; set; } @@ -292,13 +322,35 @@ public class OrganizationLicense : ILicense } /// - /// Do not extend this method. It is only here for backwards compatibility with old licenses. - /// Instead, extend the CanUse method using the ClaimsPrincipal. + /// Validates an obsolete license format using property-based validation. /// - /// - /// - /// - /// + /// + /// + /// ⚠️ DEPRECATED: This method is deprecated and should not be extended or modified. + /// It is maintained only for backward compatibility with old license formats. + /// + /// + /// This method has been replaced by a new claims-based validation system that provides: + /// - Better security through JWT claims + /// - More flexible validation rules + /// - Easier extensibility without changing the license format + /// - Better separation of concerns + /// + /// + /// To add new license validation rules: + /// 1. Add new claims to the license token in the claims-based system + /// 2. Extend the method + /// 3. Validate the new claims using the ClaimsPrincipal parameter + /// + /// + /// This method will be removed in a future version once all old licenses have been migrated + /// to the new claims-based system. + /// + /// + /// The global settings containing installation information. + /// The service used to verify the license signature. + /// When the method returns false, contains the error message explaining why the license is invalid. + /// True if the license is valid, false otherwise. private bool ObsoleteCanUse(IGlobalSettings globalSettings, ILicensingService licensingService, out string exception) { // Do not extend this method. It is only here for backwards compatibility with old licenses. @@ -392,6 +444,7 @@ public class OrganizationLicense : ILicense var usePasswordManager = claimsPrincipal.GetValue(nameof(UsePasswordManager)); var smSeats = claimsPrincipal.GetValue(nameof(SmSeats)); var smServiceAccounts = claimsPrincipal.GetValue(nameof(SmServiceAccounts)); + var useAdminSponsoredFamilies = claimsPrincipal.GetValue(nameof(UseAdminSponsoredFamilies)); return issued <= DateTime.UtcNow && expires >= DateTime.UtcNow && @@ -419,7 +472,9 @@ public class OrganizationLicense : ILicense useSecretsManager == organization.UseSecretsManager && usePasswordManager == organization.UsePasswordManager && smSeats == organization.SmSeats && - smServiceAccounts == organization.SmServiceAccounts; + smServiceAccounts == organization.SmServiceAccounts && + useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies; + } /// diff --git a/test/Core.Test/Models/Business/OrganizationLicenseTests.cs b/test/Core.Test/Models/Business/OrganizationLicenseTests.cs index 26945f533e..836df59dd8 100644 --- a/test/Core.Test/Models/Business/OrganizationLicenseTests.cs +++ b/test/Core.Test/Models/Business/OrganizationLicenseTests.cs @@ -1,9 +1,6 @@ using System.Security.Claims; -using System.Text.Json; using Bit.Core.Models.Business; -using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; @@ -12,22 +9,6 @@ namespace Bit.Core.Test.Models.Business; public class OrganizationLicenseTests { - /// - /// Verifies that when the license file is loaded from disk using the current OrganizationLicense class, - /// its hash does not change. - /// This guards against the risk that properties added in later versions are accidentally included in the hash, - /// or that a property is added without incrementing the version number. - /// - [Theory] - [BitAutoData(OrganizationLicense.CurrentLicenseFileVersion)] // Previous version (this property is 1 behind) - [BitAutoData(OrganizationLicense.CurrentLicenseFileVersion + 1)] // Current version - public void OrganizationLicense_LoadFromDisk_HashDoesNotChange(int licenseVersion) - { - var license = OrganizationLicenseFileFixtures.GetVersion(licenseVersion); - - // Compare the hash loaded from the json to the hash generated by the current class - Assert.Equal(Convert.FromBase64String(license.Hash), license.ComputeHash()); - } /// /// Verifies that when the license file is loaded from disk using the current OrganizationLicense class, @@ -52,22 +33,4 @@ public class OrganizationLicenseTests }); Assert.True(license.VerifyData(organization, claimsPrincipal, globalSettings)); } - - /// - /// Helper used to generate a new json string to be added in OrganizationLicenseFileFixtures. - /// Uncomment [Fact], run the test and copy the value of the `result` variable into OrganizationLicenseFileFixtures, - /// following the instructions in that class. - /// - // [Fact] - private void GenerateLicenseFileJsonString() - { - var organization = OrganizationLicenseFileFixtures.OrganizationFactory(); - var licensingService = Substitute.For(); - var installationId = new Guid(OrganizationLicenseFileFixtures.InstallationId); - - var license = new OrganizationLicense(organization, null, installationId, licensingService); - - var result = JsonSerializer.Serialize(license, JsonHelpers.Indented).Replace("\"", "'"); - // Put a break after this line, then copy and paste the value of `result` into OrganizationLicenseFileFixtures - } }