From 0434191bcafbddb402daaf8c29a202766bacfd20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:47:21 +0200 Subject: [PATCH 01/15] [deps] Tools: Update aws-sdk-net monorepo (#5704) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src/Core/Core.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index ce412cb47c..88dcea30fa 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -23,8 +23,8 @@ - - + + From cb2860c0c1beeb12d35c5166442d419535dd871f Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Fri, 25 Apr 2025 05:54:54 -0500 Subject: [PATCH 02/15] chore: update public api members delete xmldoc, refs PM-20520 (#5708) --- src/Api/AdminConsole/Public/Controllers/MembersController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Api/AdminConsole/Public/Controllers/MembersController.cs b/src/Api/AdminConsole/Public/Controllers/MembersController.cs index 76bd29d38e..92e5071801 100644 --- a/src/Api/AdminConsole/Public/Controllers/MembersController.cs +++ b/src/Api/AdminConsole/Public/Controllers/MembersController.cs @@ -221,8 +221,7 @@ public class MembersController : Controller /// Remove a member. /// /// - /// Permanently removes a member from the organization. This cannot be undone. - /// The user account will still remain. The user is only removed from the organization. + /// Removes a member from the organization. This cannot be undone. The user account will still remain. /// /// The identifier of the member to be removed. [HttpDelete("{id}")] From 5184d109954d9cd66df1e91b8ec1765df678396c Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:06:06 -0400 Subject: [PATCH 03/15] Create customer for client organization that was converted to BU upon unlinking (#5706) --- .../Providers/RemoveOrganizationFromProviderCommand.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs b/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs index 2c34e57a92..9a62be8dd5 100644 --- a/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs +++ b/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs @@ -110,9 +110,14 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv IEnumerable organizationOwnerEmails) { if (provider.IsBillable() && - organization.IsValidClient() && - !string.IsNullOrEmpty(organization.GatewayCustomerId)) + organization.IsValidClient()) { + // An organization converted to a business unit will not have a Customer since it was given to the business unit. + if (string.IsNullOrEmpty(organization.GatewayCustomerId)) + { + await _providerBillingService.CreateCustomerForClientOrganization(provider, organization); + } + var customer = await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions { Description = string.Empty, From 9a7fddd77c38c1013ae9e58e23127544afe92f3d Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 25 Apr 2025 13:15:26 -0400 Subject: [PATCH 04/15] Removed feature flag (#5707) --- src/Core/Constants.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index e4a8465bcb..b6cc079600 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -195,7 +195,6 @@ public static class FeatureFlagKeys public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form"; public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss"; public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss"; - public const string VaultBulkManagementAction = "vault-bulk-management-action"; public const string RestrictProviderAccess = "restrict-provider-access"; public const string SecurityTasks = "security-tasks"; public const string CipherKeyEncryption = "cipher-key-encryption"; From ad19d3d3ade994e6a167b2b17d2c065f820f8696 Mon Sep 17 00:00:00 2001 From: Brant DeBow <125889545+brant-livefront@users.noreply.github.com> Date: Mon, 28 Apr 2025 08:20:47 -0400 Subject: [PATCH 05/15] [PM-17562] Add feature flag for event-based organization integrations (#5710) * Added EventBasedOrganizationIntegrations feature flag; Added enforcement of flag at the API layer * [PM-17562] Use EventBasedOrganizationIntegrations feature flag to turn on/off event queue * Optimization that removes the need for EventRouteService (from @justindbaur) --- ...ationIntegrationConfigurationController.cs | 3 +++ .../OrganizationIntegrationController.cs | 3 +++ .../Controllers/SlackIntegrationController.cs | 3 +++ src/Core/Constants.cs | 1 + src/Events/Startup.cs | 23 +++++++++++++++---- .../Utilities/ServiceCollectionExtensions.cs | 23 +++++++++++++++---- 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs b/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs index da0151067b..848098ef00 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs @@ -1,13 +1,16 @@ using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Core; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.AdminConsole.Controllers; +[RequireFeature(FeatureFlagKeys.EventBasedOrganizationIntegrations)] [Route("organizations/{organizationId:guid}/integrations/{integrationId:guid}/configurations")] [Authorize("Application")] public class OrganizationIntegrationConfigurationController( diff --git a/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs b/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs index cb96be97c7..3b52e7a8da 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs @@ -1,8 +1,10 @@ using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Core; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -10,6 +12,7 @@ using Microsoft.AspNetCore.Mvc; namespace Bit.Api.AdminConsole.Controllers; +[RequireFeature(FeatureFlagKeys.EventBasedOrganizationIntegrations)] [Route("organizations/{organizationId:guid}/integrations")] [Authorize("Application")] public class OrganizationIntegrationController( diff --git a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs b/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs index ed6971911b..a8bef10dc6 100644 --- a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs +++ b/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Context; using Bit.Core.Enums; @@ -7,11 +8,13 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Data.Integrations; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.AdminConsole.Controllers; +[RequireFeature(FeatureFlagKeys.EventBasedOrganizationIntegrations)] [Route("organizations/{organizationId:guid}/integrations/slack")] [Authorize("Application")] public class SlackIntegrationController( diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index b6cc079600..5a7ed13843 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -108,6 +108,7 @@ public static class FeatureFlagKeys public const string PolicyRequirements = "pm-14439-policy-requirements"; public const string SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility"; public const string ScimInviteUserOptimization = "pm-16811-optimize-invite-user-flow-to-fail-fast"; + public const string EventBasedOrganizationIntegrations = "event-based-organization-integrations"; /* Auth Team */ public const string PM9112DeviceApprovalPersistence = "pm-9112-device-approval-persistence"; diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index 34ffed4ee6..bb37e240c8 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Bit.Core; using Bit.Core.AdminConsole.Services.Implementations; using Bit.Core.AdminConsole.Services.NoopImplementations; using Bit.Core.Context; @@ -62,33 +63,45 @@ public class Startup { services.AddSingleton(); } - services.AddScoped(); + if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) { + services.AddKeyedSingleton("storage"); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } else { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } } else { + services.AddKeyedSingleton("storage"); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } else { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } } + services.AddScoped(sp => + { + var featureService = sp.GetRequiredService(); + var key = featureService.IsEnabled(FeatureFlagKeys.EventBasedOrganizationIntegrations) + ? "broadcast" : "storage"; + return sp.GetRequiredKeyedService(key); + }); + services.AddScoped(); services.AddOptionality(); diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 8d48fc86ef..9883e6db47 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using AspNetCoreRateLimit; using Azure.Storage.Queues; +using Bit.Core; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.Services; @@ -332,34 +333,46 @@ public static class ServiceCollectionExtensions if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) { + services.AddKeyedSingleton("storage"); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } else { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } } else if (globalSettings.SelfHosted) { + services.AddKeyedSingleton("storage"); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } else { - services.AddSingleton(); + services.AddKeyedSingleton("broadcast"); } } else { - services.AddSingleton(); + services.AddKeyedSingleton("storage"); + services.AddKeyedSingleton("broadcast"); } + services.AddScoped(sp => + { + var featureService = sp.GetRequiredService(); + var key = featureService.IsEnabled(FeatureFlagKeys.EventBasedOrganizationIntegrations) + ? "broadcast" : "storage"; + return sp.GetRequiredKeyedService(key); + }); if (CoreHelpers.SettingHasValue(globalSettings.Attachment.ConnectionString)) { From 60f61893140434f61e34f4aa4d8e240e699d4cdc Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:41:49 +0200 Subject: [PATCH 06/15] Move feature flags owned by Data Insights and Reporting team into their own section (#5691) Co-authored-by: Daniel James Smith --- src/Core/Constants.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 5a7ed13843..36c054b55f 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -151,6 +151,10 @@ public static class FeatureFlagKeys public const string PM18770_EnableOrganizationBusinessUnitConversion = "pm-18770-enable-organization-business-unit-conversion"; public const string PM199566_UpdateMSPToChargeAutomatically = "pm-199566-update-msp-to-charge-automatically"; + /* Data Insights and Reporting Team */ + public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application"; + public const string EnableRiskInsightsNotifications = "enable-risk-insights-notifications"; + /* Key Management Team */ public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair"; public const string PM4154BulkEncryptionService = "PM-4154-bulk-encryption-service"; @@ -187,8 +191,6 @@ public static class FeatureFlagKeys /* Tools Team */ public const string ItemShare = "item-share"; - public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application"; - public const string EnableRiskInsightsNotifications = "enable-risk-insights-notifications"; public const string DesktopSendUIRefresh = "desktop-send-ui-refresh"; /* Vault Team */ From 12fc9dffd4c6ed7e035a310836d599663c41d888 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Mon, 28 Apr 2025 09:55:55 -0500 Subject: [PATCH 07/15] [PM-20586] - Fixing allowing seats to increase to limit. (#5705) --- .../PasswordManager/InviteUsersPasswordManagerValidator.cs | 2 +- .../PasswordManager/PasswordManagerSubscriptionUpdate.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/InviteUsersPasswordManagerValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/InviteUsersPasswordManagerValidator.cs index 1867a2808e..6a8ec8e6d3 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/InviteUsersPasswordManagerValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/InviteUsersPasswordManagerValidator.cs @@ -44,7 +44,7 @@ public class InviteUsersPasswordManagerValidator( return new Invalid(new PasswordManagerMustHaveSeatsError(subscriptionUpdate)); } - if (subscriptionUpdate.MaxSeatsReached) + if (subscriptionUpdate.MaxSeatsExceeded) { return new Invalid( new PasswordManagerSeatLimitHasBeenReachedError(subscriptionUpdate)); diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs index f6126fd8f5..d7c61c6c81 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs @@ -48,6 +48,11 @@ public class PasswordManagerSubscriptionUpdate /// public bool MaxSeatsReached => UpdatedSeatTotal.HasValue && MaxAutoScaleSeats.HasValue && UpdatedSeatTotal.Value >= MaxAutoScaleSeats.Value; + /// + /// If the new seat total exceeds the organization's auto-scale seat limit + /// + public bool MaxSeatsExceeded => UpdatedSeatTotal.HasValue && MaxAutoScaleSeats.HasValue && UpdatedSeatTotal.Value > MaxAutoScaleSeats.Value; + public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; } public InviteOrganization InviteOrganization { get; } From 07a2c0e9d25e86524ce6bc10139a4f07bea9cb5f Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:21:52 +0100 Subject: [PATCH 08/15] [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 * Remove the unsed failing test Signed-off-by: Cy Okeke * Remove unused method Signed-off-by: Cy Okeke --------- Signed-off-by: Cy Okeke Co-authored-by: Jonas Hendrickx --- src/Core/Billing/Licenses/LicenseConstants.cs | 1 + .../OrganizationLicenseClaimsFactory.cs | 2 + .../Models/Business/OrganizationLicense.cs | 69 +++++++++++++++++-- .../Business/OrganizationLicenseTests.cs | 37 ---------- 4 files changed, 65 insertions(+), 44 deletions(-) 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 - } } From 00b9ba2392840e6a7a1d4502a76b526010f04656 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:50:40 -0400 Subject: [PATCH 09/15] Allow for deletion of pending providers (#5679) --- .../Controllers/ProvidersController.cs | 20 +++++++---- .../AdminConsole/Views/Providers/Edit.cshtml | 34 +++++++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/Admin/AdminConsole/Controllers/ProvidersController.cs b/src/Admin/AdminConsole/Controllers/ProvidersController.cs index 264e9df069..dd4332358c 100644 --- a/src/Admin/AdminConsole/Controllers/ProvidersController.cs +++ b/src/Admin/AdminConsole/Controllers/ProvidersController.cs @@ -470,6 +470,19 @@ public class ProvidersController : Controller [RequirePermission(Permission.Provider_Edit)] public async Task Delete(Guid id, string providerName) { + var provider = await _providerRepository.GetByIdAsync(id); + + if (provider is null) + { + return BadRequest("Provider does not exist"); + } + + if (provider.Status == ProviderStatusType.Pending) + { + await _providerService.DeleteAsync(provider); + return NoContent(); + } + if (string.IsNullOrWhiteSpace(providerName)) { return BadRequest("Invalid provider name"); @@ -482,13 +495,6 @@ public class ProvidersController : Controller return BadRequest("You must unlink all clients before you can delete a provider"); } - var provider = await _providerRepository.GetByIdAsync(id); - - if (provider is null) - { - return BadRequest("Provider does not exist"); - } - if (!string.Equals(providerName.Trim(), provider.DisplayName(), StringComparison.OrdinalIgnoreCase)) { return BadRequest("Invalid provider name"); diff --git a/src/Admin/AdminConsole/Views/Providers/Edit.cshtml b/src/Admin/AdminConsole/Views/Providers/Edit.cshtml index ce215e1575..d2a9ed1f62 100644 --- a/src/Admin/AdminConsole/Views/Providers/Edit.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/Edit.cshtml @@ -183,17 +183,29 @@

Delete provider

- + + @if (Model.Provider.Status == ProviderStatusType.Pending) + { + + } + else + { + + }