diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 24786868ec..d6ad3d274c 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -17,6 +17,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Billing.Pricing; using Bit.Core.Context; using Bit.Core.Entities; diff --git a/src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs b/src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs index fc6148a609..9b4cfb84ae 100644 --- a/src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs +++ b/src/Core/Billing/Licenses/Extensions/LicenseExtensions.cs @@ -7,7 +7,9 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Licenses.Attributes; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Models.Business; +using Bit.Core.Settings; using Bit.Core.Utilities; namespace Bit.Core.Billing.Licenses.Extensions; @@ -223,6 +225,116 @@ public static class OrganizationLicenseExtensions subscriptionInfo?.Subscription == null ? org.PlanType != PlanType.Custom || !org.ExpirationDate.HasValue : subscriptionInfo.Subscription.TrialEndDate.HasValue && subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow; + + public static bool CanUse( + this OrganizationLicense license, + IGlobalSettings globalSettings, + ClaimsPrincipal claimsPrincipal, + out string exception) + { + var errorMessages = new StringBuilder(); + + var enabled = claimsPrincipal.GetValue(nameof(OrganizationLicense.Enabled)); + if (!enabled) + { + errorMessages.AppendLine("Your cloud-hosted organization is currently disabled."); + } + + var installationId = claimsPrincipal.GetValue(nameof(OrganizationLicense.InstallationId)); + if (installationId != globalSettings.Installation.Id) + { + errorMessages.AppendLine("The installation ID does not match the current installation."); + } + + var selfHost = claimsPrincipal.GetValue(nameof(OrganizationLicense.SelfHost)); + if (!selfHost) + { + errorMessages.AppendLine("The license does not allow for on-premise hosting of organizations."); + } + + var licenseType = claimsPrincipal.GetValue(nameof(OrganizationLicense.LicenseType)); + if (licenseType != LicenseType.Organization) + { + errorMessages.AppendLine("Premium licenses cannot be applied to an organization. " + + "Upload this license from your personal account settings page."); + } + + if (errorMessages.Length > 0) + { + exception = $"Invalid license. {errorMessages.ToString().TrimEnd()}"; + return false; + } + + exception = ""; + return true; + } + + public static bool VerifyData( + this OrganizationLicense license, + Organization organization, + ClaimsPrincipal claimsPrincipal, + IGlobalSettings globalSettings) + { + var issued = claimsPrincipal.GetValue(nameof(OrganizationLicense.Issued)); + var expires = claimsPrincipal.GetValue(nameof(OrganizationLicense.Expires)); + var installationId = claimsPrincipal.GetValue(nameof(OrganizationLicense.InstallationId)); + var licenseKey = claimsPrincipal.GetValue(nameof(OrganizationLicense.LicenseKey)); + var enabled = claimsPrincipal.GetValue(nameof(OrganizationLicense.Enabled)); + var planType = claimsPrincipal.GetValue(nameof(OrganizationLicense.PlanType)); + var seats = claimsPrincipal.GetValue(nameof(OrganizationLicense.Seats)); + var maxCollections = claimsPrincipal.GetValue(nameof(OrganizationLicense.MaxCollections)); + var useGroups = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseGroups)); + var useDirectory = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseDirectory)); + var useTotp = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseTotp)); + var selfHost = claimsPrincipal.GetValue(nameof(OrganizationLicense.SelfHost)); + var name = claimsPrincipal.GetValue(nameof(OrganizationLicense.Name)); + var usersGetPremium = claimsPrincipal.GetValue(nameof(OrganizationLicense.UsersGetPremium)); + var useEvents = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseEvents)); + var use2fa = claimsPrincipal.GetValue(nameof(OrganizationLicense.Use2fa)); + var useApi = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseApi)); + var usePolicies = claimsPrincipal.GetValue(nameof(OrganizationLicense.UsePolicies)); + var useSso = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseSso)); + var useResetPassword = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseResetPassword)); + var useKeyConnector = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseKeyConnector)); + var useScim = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseScim)); + var useCustomPermissions = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseCustomPermissions)); + var useSecretsManager = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseSecretsManager)); + var usePasswordManager = claimsPrincipal.GetValue(nameof(OrganizationLicense.UsePasswordManager)); + var smSeats = claimsPrincipal.GetValue(nameof(OrganizationLicense.SmSeats)); + var smServiceAccounts = claimsPrincipal.GetValue(nameof(OrganizationLicense.SmServiceAccounts)); + var useAdminSponsoredFamilies = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseAdminSponsoredFamilies)); + var useOrganizationDomains = claimsPrincipal.GetValue(nameof(OrganizationLicense.UseOrganizationDomains)); + + return issued <= DateTime.UtcNow && + expires >= DateTime.UtcNow && + installationId == globalSettings.Installation.Id && + licenseKey == organization.LicenseKey && + enabled == organization.Enabled && + planType == organization.PlanType && + seats == organization.Seats && + maxCollections == organization.MaxCollections && + useGroups == organization.UseGroups && + useDirectory == organization.UseDirectory && + useTotp == organization.UseTotp && + selfHost == organization.SelfHost && + name == organization.Name && + usersGetPremium == organization.UsersGetPremium && + useEvents == organization.UseEvents && + use2fa == organization.Use2fa && + useApi == organization.UseApi && + usePolicies == organization.UsePolicies && + useSso == organization.UseSso && + useResetPassword == organization.UseResetPassword && + useKeyConnector == organization.UseKeyConnector && + useScim == organization.UseScim && + useCustomPermissions == organization.UseCustomPermissions && + useSecretsManager == organization.UseSecretsManager && + usePasswordManager == organization.UsePasswordManager && + smSeats == organization.SmSeats && + smServiceAccounts == organization.SmServiceAccounts && + useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies && + useOrganizationDomains == organization.UseOrganizationDomains; + } } public static class UserLicenseExtensions @@ -243,4 +355,40 @@ public static class UserLicenseExtensions subscriptionInfo != null && (subscriptionInfo?.Subscription?.TrialEndDate.HasValue ?? false) && subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow; + + public static bool CanUse(this UserLicense license, User user, ClaimsPrincipal claimsPrincipal, out string exception) + { + var errorMessages = new StringBuilder(); + + if (!user.EmailVerified) + { + errorMessages.AppendLine("The user's email is not verified."); + } + + var email = claimsPrincipal.GetValue(nameof(UserLicense.Email)); + if (!email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase)) + { + errorMessages.AppendLine("The user's email does not match the license email."); + } + + if (errorMessages.Length > 0) + { + exception = $"Invalid license. {errorMessages.ToString().TrimEnd()}"; + return false; + } + + exception = ""; + return true; + } + + public static bool VerifyData(this UserLicense license, User user, ClaimsPrincipal claimsPrincipal) + { + var licenseKey = claimsPrincipal.GetValue(nameof(UserLicense.LicenseKey)); + var premium = claimsPrincipal.GetValue(nameof(UserLicense.Premium)); + var email = claimsPrincipal.GetValue(nameof(UserLicense.Email)); + + return licenseKey == user.LicenseKey && + premium == user.Premium && + email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase); + } } diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index e8d7f1ae40..1b9b0077ca 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -1,13 +1,9 @@ -using System.Security.Claims; -using System.Text; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Licenses.Attributes; using Bit.Core.Billing.Licenses.Extensions; -using Bit.Core.Enums; using Bit.Core.Services; -using Bit.Core.Settings; namespace Bit.Core.Models.Business; @@ -17,34 +13,6 @@ public class OrganizationLicense : BaseLicense { } - /// - /// 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) { @@ -217,111 +185,5 @@ public class OrganizationLicense : BaseLicense - public bool CanUse( - IGlobalSettings globalSettings, - ClaimsPrincipal claimsPrincipal, - out string exception) - { - var errorMessages = new StringBuilder(); - var enabled = claimsPrincipal.GetValue(nameof(Enabled)); - if (!enabled) - { - errorMessages.AppendLine("Your cloud-hosted organization is currently disabled."); - } - - var installationId = claimsPrincipal.GetValue(nameof(InstallationId)); - if (installationId != globalSettings.Installation.Id) - { - errorMessages.AppendLine("The installation ID does not match the current installation."); - } - - var selfHost = claimsPrincipal.GetValue(nameof(SelfHost)); - if (!selfHost) - { - errorMessages.AppendLine("The license does not allow for on-premise hosting of organizations."); - } - - var licenseType = claimsPrincipal.GetValue(nameof(LicenseType)); - if (licenseType != LicenseType.Organization) - { - errorMessages.AppendLine("Premium licenses cannot be applied to an organization. " + - "Upload this license from your personal account settings page."); - } - - if (errorMessages.Length > 0) - { - exception = $"Invalid license. {errorMessages.ToString().TrimEnd()}"; - return false; - } - - exception = ""; - return true; - } - - public bool VerifyData( - Organization organization, - ClaimsPrincipal claimsPrincipal, - IGlobalSettings globalSettings) - { - var issued = claimsPrincipal.GetValue(nameof(Issued)); - var expires = claimsPrincipal.GetValue(nameof(Expires)); - var installationId = claimsPrincipal.GetValue(nameof(InstallationId)); - var licenseKey = claimsPrincipal.GetValue(nameof(LicenseKey)); - var enabled = claimsPrincipal.GetValue(nameof(Enabled)); - var planType = claimsPrincipal.GetValue(nameof(PlanType)); - var seats = claimsPrincipal.GetValue(nameof(Seats)); - var maxCollections = claimsPrincipal.GetValue(nameof(MaxCollections)); - var useGroups = claimsPrincipal.GetValue(nameof(UseGroups)); - var useDirectory = claimsPrincipal.GetValue(nameof(UseDirectory)); - var useTotp = claimsPrincipal.GetValue(nameof(UseTotp)); - var selfHost = claimsPrincipal.GetValue(nameof(SelfHost)); - var name = claimsPrincipal.GetValue(nameof(Name)); - var usersGetPremium = claimsPrincipal.GetValue(nameof(UsersGetPremium)); - var useEvents = claimsPrincipal.GetValue(nameof(UseEvents)); - var use2fa = claimsPrincipal.GetValue(nameof(Use2fa)); - var useApi = claimsPrincipal.GetValue(nameof(UseApi)); - var usePolicies = claimsPrincipal.GetValue(nameof(UsePolicies)); - var useSso = claimsPrincipal.GetValue(nameof(UseSso)); - var useResetPassword = claimsPrincipal.GetValue(nameof(UseResetPassword)); - var useKeyConnector = claimsPrincipal.GetValue(nameof(UseKeyConnector)); - var useScim = claimsPrincipal.GetValue(nameof(UseScim)); - var useCustomPermissions = claimsPrincipal.GetValue(nameof(UseCustomPermissions)); - var useSecretsManager = claimsPrincipal.GetValue(nameof(UseSecretsManager)); - var usePasswordManager = claimsPrincipal.GetValue(nameof(UsePasswordManager)); - var smSeats = claimsPrincipal.GetValue(nameof(SmSeats)); - var smServiceAccounts = claimsPrincipal.GetValue(nameof(SmServiceAccounts)); - var useAdminSponsoredFamilies = claimsPrincipal.GetValue(nameof(UseAdminSponsoredFamilies)); - var useOrganizationDomains = claimsPrincipal.GetValue(nameof(UseOrganizationDomains)); - - return issued <= DateTime.UtcNow && - expires >= DateTime.UtcNow && - installationId == globalSettings.Installation.Id && - licenseKey == organization.LicenseKey && - enabled == organization.Enabled && - planType == organization.PlanType && - seats == organization.Seats && - maxCollections == organization.MaxCollections && - useGroups == organization.UseGroups && - useDirectory == organization.UseDirectory && - useTotp == organization.UseTotp && - selfHost == organization.SelfHost && - name == organization.Name && - usersGetPremium == organization.UsersGetPremium && - useEvents == organization.UseEvents && - use2fa == organization.Use2fa && - useApi == organization.UseApi && - usePolicies == organization.UsePolicies && - useSso == organization.UseSso && - useResetPassword == organization.UseResetPassword && - useKeyConnector == organization.UseKeyConnector && - useScim == organization.UseScim && - useCustomPermissions == organization.UseCustomPermissions && - useSecretsManager == organization.UseSecretsManager && - usePasswordManager == organization.UsePasswordManager && - smSeats == organization.SmSeats && - smServiceAccounts == organization.SmServiceAccounts && - useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies && - useOrganizationDomains == organization.UseOrganizationDomains; - } } diff --git a/src/Core/Models/Business/UserLicense.cs b/src/Core/Models/Business/UserLicense.cs index 36e5bccfb8..4ba37eab55 100644 --- a/src/Core/Models/Business/UserLicense.cs +++ b/src/Core/Models/Business/UserLicense.cs @@ -1,6 +1,4 @@ -using System.Security.Claims; -using System.Text; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Bit.Core.Billing.Licenses.Attributes; using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Entities; @@ -68,39 +66,5 @@ public class UserLicense : BaseLicense get => Version == 1; } - public bool CanUse(User user, ClaimsPrincipal claimsPrincipal, out string exception) - { - var errorMessages = new StringBuilder(); - if (!user.EmailVerified) - { - errorMessages.AppendLine("The user's email is not verified."); - } - - var email = claimsPrincipal.GetValue(nameof(Email)); - if (!email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase)) - { - errorMessages.AppendLine("The user's email does not match the license email."); - } - - if (errorMessages.Length > 0) - { - exception = $"Invalid license. {errorMessages.ToString().TrimEnd()}"; - return false; - } - - exception = ""; - return true; - } - - public bool VerifyData(User user, ClaimsPrincipal claimsPrincipal) - { - var licenseKey = claimsPrincipal.GetValue(nameof(LicenseKey)); - var premium = claimsPrincipal.GetValue(nameof(Premium)); - var email = claimsPrincipal.GetValue(nameof(Email)); - - return licenseKey == user.LicenseKey && - premium == user.Premium && - email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase); - } } diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs index 7ca140793b..dee5686d41 100644 --- a/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs @@ -2,6 +2,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations; diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index bf90190ee6..265adf59bf 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -15,6 +15,7 @@ using Bit.Core.Auth.Models; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; diff --git a/test/Core.Test/Models/Business/OrganizationLicenseTests.cs b/test/Core.Test/Models/Business/OrganizationLicenseTests.cs index 836df59dd8..dbde330b74 100644 --- a/test/Core.Test/Models/Business/OrganizationLicenseTests.cs +++ b/test/Core.Test/Models/Business/OrganizationLicenseTests.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Models.Business; using Bit.Core.Settings; using Bit.Test.Common.AutoFixture.Attributes;