mirror of
https://github.com/bitwarden/server.git
synced 2025-04-30 01:02:21 -05:00
[PM-18569]Add admin sponsored families to organization license (#5569)
* WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Add `Notes` column to `OrganizationSponsorships` table * Add feature flag to `CreateAdminInitiatedSponsorshipHandler` * Unit tests for `CreateSponsorshipHandler` * More tests for `CreateSponsorshipHandler` * Forgot to add `Notes` column to `OrganizationSponsorships` table in the migration script * `CreateAdminInitiatedSponsorshipHandler` unit tests * Fix `CreateSponsorshipCommandTests` * Encrypt the notes field * Wrong business logic checking for invalid permissions. * Wrong business logic checking for invalid permissions. * Remove design patterns * duplicate definition in Constants.cs * initial commit * Merge Change with pm-17830 and use the property * Add the new property to download licence * Add the new property Signed-off-by: Cy Okeke <cokeke@bitwarden.com> * Remove the unsed failing test Signed-off-by: Cy Okeke <cokeke@bitwarden.com> * Remove unused method Signed-off-by: Cy Okeke <cokeke@bitwarden.com> --------- Signed-off-by: Cy Okeke <cokeke@bitwarden.com> Co-authored-by: Jonas Hendrickx <jhendrickx@bitwarden.com>
This commit is contained in:
parent
12fc9dffd4
commit
07a2c0e9d2
@ -41,6 +41,7 @@ public static class OrganizationLicenseConstants
|
|||||||
public const string Refresh = nameof(Refresh);
|
public const string Refresh = nameof(Refresh);
|
||||||
public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod);
|
public const string ExpirationWithoutGracePeriod = nameof(ExpirationWithoutGracePeriod);
|
||||||
public const string Trial = nameof(Trial);
|
public const string Trial = nameof(Trial);
|
||||||
|
public const string UseAdminSponsoredFamilies = nameof(UseAdminSponsoredFamilies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UserLicenseConstants
|
public static class UserLicenseConstants
|
||||||
|
@ -53,6 +53,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
|||||||
new(nameof(OrganizationLicenseConstants.Refresh), refresh.ToString(CultureInfo.InvariantCulture)),
|
new(nameof(OrganizationLicenseConstants.Refresh), refresh.ToString(CultureInfo.InvariantCulture)),
|
||||||
new(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod), expirationWithoutGracePeriod.ToString(CultureInfo.InvariantCulture)),
|
new(nameof(OrganizationLicenseConstants.ExpirationWithoutGracePeriod), expirationWithoutGracePeriod.ToString(CultureInfo.InvariantCulture)),
|
||||||
new(nameof(OrganizationLicenseConstants.Trial), trial.ToString()),
|
new(nameof(OrganizationLicenseConstants.Trial), trial.ToString()),
|
||||||
|
new(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (entity.Name is not null)
|
if (entity.Name is not null)
|
||||||
@ -109,6 +110,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory<Organizati
|
|||||||
{
|
{
|
||||||
claims.Add(new Claim(nameof(OrganizationLicenseConstants.SmServiceAccounts), entity.SmServiceAccounts.ToString()));
|
claims.Add(new Claim(nameof(OrganizationLicenseConstants.SmServiceAccounts), entity.SmServiceAccounts.ToString()));
|
||||||
}
|
}
|
||||||
|
claims.Add(new Claim(nameof(OrganizationLicenseConstants.UseAdminSponsoredFamilies), entity.UseAdminSponsoredFamilies.ToString()));
|
||||||
|
|
||||||
return Task.FromResult(claims);
|
return Task.FromResult(claims);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,34 @@ public class OrganizationLicense : ILicense
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OrganizationLicense"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// ⚠️ DEPRECATED: This constructor and the entire property-based licensing system is deprecated.
|
||||||
|
/// Do not add new properties to this constructor or extend its functionality.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 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.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 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 <see cref="CanUse"/> and <see cref="VerifyData"/> methods
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This constructor is maintained only for backward compatibility with existing licenses.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="org">The organization to create the license for.</param>
|
||||||
|
/// <param name="subscriptionInfo">Information about the organization's subscription.</param>
|
||||||
|
/// <param name="installationId">The ID of the current installation.</param>
|
||||||
|
/// <param name="licenseService">The service used to sign the license.</param>
|
||||||
|
/// <param name="version">Optional version number for the license format.</param>
|
||||||
public OrganizationLicense(Organization org, SubscriptionInfo subscriptionInfo, Guid installationId,
|
public OrganizationLicense(Organization org, SubscriptionInfo subscriptionInfo, Guid installationId,
|
||||||
ILicensingService licenseService, int? version = null)
|
ILicensingService licenseService, int? version = null)
|
||||||
{
|
{
|
||||||
@ -105,6 +133,7 @@ public class OrganizationLicense : ILicense
|
|||||||
Trial = false;
|
Trial = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UseAdminSponsoredFamilies = org.UseAdminSponsoredFamilies;
|
||||||
Hash = Convert.ToBase64String(ComputeHash());
|
Hash = Convert.ToBase64String(ComputeHash());
|
||||||
Signature = Convert.ToBase64String(licenseService.SignLicense(this));
|
Signature = Convert.ToBase64String(licenseService.SignLicense(this));
|
||||||
}
|
}
|
||||||
@ -153,6 +182,7 @@ public class OrganizationLicense : ILicense
|
|||||||
|
|
||||||
public bool Trial { get; set; }
|
public bool Trial { get; set; }
|
||||||
public LicenseType? LicenseType { get; set; }
|
public LicenseType? LicenseType { get; set; }
|
||||||
|
public bool UseAdminSponsoredFamilies { get; set; }
|
||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
public string Signature { get; set; }
|
public string Signature { get; set; }
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
@ -292,13 +322,35 @@ public class OrganizationLicense : ILicense
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do not extend this method. It is only here for backwards compatibility with old licenses.
|
/// Validates an obsolete license format using property-based validation.
|
||||||
/// Instead, extend the CanUse method using the ClaimsPrincipal.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="globalSettings"></param>
|
/// <remarks>
|
||||||
/// <param name="licensingService"></param>
|
/// <para>
|
||||||
/// <param name="exception"></param>
|
/// ⚠️ DEPRECATED: This method is deprecated and should not be extended or modified.
|
||||||
/// <returns></returns>
|
/// It is maintained only for backward compatibility with old license formats.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 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
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// To add new license validation rules:
|
||||||
|
/// 1. Add new claims to the license token in the claims-based system
|
||||||
|
/// 2. Extend the <see cref="CanUse(IGlobalSettings, ILicensingService, ClaimsPrincipal, out string)"/> method
|
||||||
|
/// 3. Validate the new claims using the ClaimsPrincipal parameter
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// This method will be removed in a future version once all old licenses have been migrated
|
||||||
|
/// to the new claims-based system.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="globalSettings">The global settings containing installation information.</param>
|
||||||
|
/// <param name="licensingService">The service used to verify the license signature.</param>
|
||||||
|
/// <param name="exception">When the method returns false, contains the error message explaining why the license is invalid.</param>
|
||||||
|
/// <returns>True if the license is valid, false otherwise.</returns>
|
||||||
private bool ObsoleteCanUse(IGlobalSettings globalSettings, ILicensingService licensingService, out string exception)
|
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.
|
// 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<bool>(nameof(UsePasswordManager));
|
var usePasswordManager = claimsPrincipal.GetValue<bool>(nameof(UsePasswordManager));
|
||||||
var smSeats = claimsPrincipal.GetValue<int?>(nameof(SmSeats));
|
var smSeats = claimsPrincipal.GetValue<int?>(nameof(SmSeats));
|
||||||
var smServiceAccounts = claimsPrincipal.GetValue<int?>(nameof(SmServiceAccounts));
|
var smServiceAccounts = claimsPrincipal.GetValue<int?>(nameof(SmServiceAccounts));
|
||||||
|
var useAdminSponsoredFamilies = claimsPrincipal.GetValue<bool>(nameof(UseAdminSponsoredFamilies));
|
||||||
|
|
||||||
return issued <= DateTime.UtcNow &&
|
return issued <= DateTime.UtcNow &&
|
||||||
expires >= DateTime.UtcNow &&
|
expires >= DateTime.UtcNow &&
|
||||||
@ -419,7 +472,9 @@ public class OrganizationLicense : ILicense
|
|||||||
useSecretsManager == organization.UseSecretsManager &&
|
useSecretsManager == organization.UseSecretsManager &&
|
||||||
usePasswordManager == organization.UsePasswordManager &&
|
usePasswordManager == organization.UsePasswordManager &&
|
||||||
smSeats == organization.SmSeats &&
|
smSeats == organization.SmSeats &&
|
||||||
smServiceAccounts == organization.SmServiceAccounts;
|
smServiceAccounts == organization.SmServiceAccounts &&
|
||||||
|
useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -12,22 +9,6 @@ namespace Bit.Core.Test.Models.Business;
|
|||||||
|
|
||||||
public class OrganizationLicenseTests
|
public class OrganizationLicenseTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies that when the license file is loaded from disk using the current OrganizationLicense class,
|
/// 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));
|
Assert.True(license.VerifyData(organization, claimsPrincipal, globalSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
// [Fact]
|
|
||||||
private void GenerateLicenseFileJsonString()
|
|
||||||
{
|
|
||||||
var organization = OrganizationLicenseFileFixtures.OrganizationFactory();
|
|
||||||
var licensingService = Substitute.For<ILicensingService>();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user