From 7cd289ecab34a9f9a33e8908cfb31932cbc0078d Mon Sep 17 00:00:00 2001
From: John Harrington <84741727+harr1424@users.noreply.github.com>
Date: Tue, 24 Feb 2026 12:58:38 -0700
Subject: [PATCH 1/2] initial work to consolidate Send Policies
---
src/Core/AdminConsole/Enums/PolicyType.cs | 3 +-
...OptionsPolicyData.cs => SendPolicyData.cs} | 5 +-
.../DisableSendPolicyRequirement.cs | 27 ------
.../SendOptionsPolicyRequirement.cs | 34 --------
.../SendPolicyRequirement.cs | 39 +++++++++
.../PolicyServiceCollectionExtensions.cs | 3 +-
.../Utilities/PolicyDataValidator.cs | 2 +-
.../Services/SendValidationService.cs | 10 +--
...isableSendPolicyRequirementFactoryTests.cs | 32 -------
...endOptionsPolicyRequirementFactoryTests.cs | 49 -----------
.../SendPolicyRequirementFactoryTests.cs | 85 +++++++++++++++++++
..._ConsolidateDisableSendIntoSendOptions.sql | 42 +++++++++
.../2026-02-FinalizationMigration.sql | 11 +++
13 files changed, 190 insertions(+), 152 deletions(-)
rename src/Core/AdminConsole/Models/Data/Organizations/Policies/{SendOptionsPolicyData.cs => SendPolicyData.cs} (62%)
delete mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirement.cs
delete mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirement.cs
create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
delete mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirementFactoryTests.cs
delete mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirementFactoryTests.cs
create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
create mode 100644 util/Migrator/DbScripts/2026-02-24_00_ConsolidateDisableSendIntoSendOptions.sql
create mode 100644 util/Migrator/DbScripts_finalization/2026-02-FinalizationMigration.sql
diff --git a/src/Core/AdminConsole/Enums/PolicyType.cs b/src/Core/AdminConsole/Enums/PolicyType.cs
index bd6daf7cdffa..d60af0b01979 100644
--- a/src/Core/AdminConsole/Enums/PolicyType.cs
+++ b/src/Core/AdminConsole/Enums/PolicyType.cs
@@ -8,6 +8,7 @@ public enum PolicyType : byte
SingleOrg = 3,
RequireSso = 4,
OrganizationDataOwnership = 5,
+ [Obsolete("Consolidated into SendOptions (type 7). Use SendOptions policy with SendPolicyData.DisableSend.")]
DisableSend = 6,
SendOptions = 7,
ResetPassword = 8,
@@ -41,7 +42,7 @@ public static string GetName(this PolicyType type)
PolicyType.RequireSso => "Require single sign-on authentication",
PolicyType.OrganizationDataOwnership => "Enforce organization data ownership",
PolicyType.DisableSend => "Remove Send",
- PolicyType.SendOptions => "Send options",
+ PolicyType.SendOptions => "Send",
PolicyType.ResetPassword => "Account recovery administration",
PolicyType.MaximumVaultTimeout => "Vault timeout",
PolicyType.DisablePersonalVaultExport => "Remove individual vault export",
diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
similarity index 62%
rename from src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs
rename to src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
index 57a8544b40b0..7e4046b9e82f 100644
--- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs
+++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
@@ -2,8 +2,11 @@
namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
-public class SendOptionsPolicyData : IPolicyDataModel
+public class SendPolicyData : IPolicyDataModel
{
+ [Display(Name = "DisableSend")]
+ public bool DisableSend { get; set; }
+
[Display(Name = "DisableHideEmail")]
public bool DisableHideEmail { get; set; }
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirement.cs
deleted file mode 100644
index 1cb7f4f6196b..000000000000
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirement.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
-
-namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-
-///
-/// Policy requirements for the Disable Send policy.
-///
-public class DisableSendPolicyRequirement : IPolicyRequirement
-{
- ///
- /// Indicates whether Send is disabled for the user. If true, the user should not be able to create or edit Sends.
- /// They may still delete existing Sends.
- ///
- public bool DisableSend { get; init; }
-}
-
-public class DisableSendPolicyRequirementFactory : BasePolicyRequirementFactory
-{
- public override PolicyType PolicyType => PolicyType.DisableSend;
-
- public override DisableSendPolicyRequirement Create(IEnumerable policyDetails)
- {
- var result = new DisableSendPolicyRequirement { DisableSend = policyDetails.Any() };
- return result;
- }
-}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirement.cs
deleted file mode 100644
index 9ba11c11df16..000000000000
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirement.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
-
-namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-
-///
-/// Policy requirements for the Send Options policy.
-///
-public class SendOptionsPolicyRequirement : IPolicyRequirement
-{
- ///
- /// Indicates whether the user is prohibited from hiding their email from the recipient of a Send.
- ///
- public bool DisableHideEmail { get; init; }
-}
-
-public class SendOptionsPolicyRequirementFactory : BasePolicyRequirementFactory
-{
- public override PolicyType PolicyType => PolicyType.SendOptions;
-
- public override SendOptionsPolicyRequirement Create(IEnumerable policyDetails)
- {
- var result = policyDetails
- .Select(p => p.GetDataModel())
- .Aggregate(
- new SendOptionsPolicyRequirement(),
- (result, data) => new SendOptionsPolicyRequirement
- {
- DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail
- });
-
- return result;
- }
-}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
new file mode 100644
index 000000000000..0aa973f8584e
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
@@ -0,0 +1,39 @@
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
+
+///
+/// Policy requirements for the Send policy.
+///
+public class SendPolicyRequirement : IPolicyRequirement
+{
+ ///
+ /// Indicates whether Send is disabled for the org. If true, the org users should not be able to create or edit Sends.
+ /// They may still delete existing Sends.
+ ///
+ public bool DisableSend { get; init; }
+
+ ///
+ /// Indicates whether the org users are prohibited from hiding their email from the recipient of a Send.
+ ///
+ public bool DisableHideEmail { get; init; }
+}
+
+public class SendPolicyRequirementFactory : BasePolicyRequirementFactory
+{
+ public override PolicyType PolicyType => PolicyType.SendOptions;
+
+ public override SendPolicyRequirement Create(IEnumerable policyDetails)
+ {
+ return policyDetails
+ .Select(p => p.GetDataModel())
+ .Aggregate(
+ new SendPolicyRequirement(),
+ (result, data) => new SendPolicyRequirement
+ {
+ DisableSend = result.DisableSend || data.DisableSend,
+ DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail
+ });
+ }
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
index a7657dc71477..7b48bd23d4be 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs
@@ -67,8 +67,7 @@ private static void AddPolicyUpdateEvents(this IServiceCollection services)
private static void AddPolicyRequirements(this IServiceCollection services)
{
- services.AddScoped, DisableSendPolicyRequirementFactory>();
- services.AddScoped, SendOptionsPolicyRequirementFactory>();
+ services.AddScoped, SendPolicyRequirementFactory>();
services.AddScoped, ResetPasswordPolicyRequirementFactory>();
services.AddScoped, OrganizationDataOwnershipPolicyRequirementFactory>();
services.AddScoped, RequireSsoPolicyRequirementFactory>();
diff --git a/src/Core/AdminConsole/Utilities/PolicyDataValidator.cs b/src/Core/AdminConsole/Utilities/PolicyDataValidator.cs
index d533ca88cf8b..66b4db5866db 100644
--- a/src/Core/AdminConsole/Utilities/PolicyDataValidator.cs
+++ b/src/Core/AdminConsole/Utilities/PolicyDataValidator.cs
@@ -35,7 +35,7 @@ public static class PolicyDataValidator
ValidateModel(masterPasswordData, policyType);
break;
case PolicyType.SendOptions:
- CoreHelpers.LoadClassFromJsonData(json);
+ CoreHelpers.LoadClassFromJsonData(json);
break;
case PolicyType.ResetPassword:
CoreHelpers.LoadClassFromJsonData(json);
diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
index bd987bb396bc..a951d6233367 100644
--- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
+++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
@@ -77,7 +77,7 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send)
if (send.HideEmail.GetValueOrDefault())
{
var sendOptionsPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId.Value, PolicyType.SendOptions);
- if (sendOptionsPolicies.Any(p => CoreHelpers.LoadClassFromJsonData(p.PolicyData)?.DisableHideEmail ?? false))
+ if (sendOptionsPolicies.Any(p => CoreHelpers.LoadClassFromJsonData(p.PolicyData)?.DisableHideEmail ?? false))
{
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}
@@ -91,14 +91,14 @@ public async Task ValidateUserCanSaveAsync_vNext(Guid? userId, Send send)
return;
}
- var disableSendRequirement = await _policyRequirementQuery.GetAsync(userId.Value);
- if (disableSendRequirement.DisableSend)
+ var sendRequirement = await _policyRequirementQuery.GetAsync(userId.Value);
+
+ if (sendRequirement.DisableSend)
{
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}
- var sendOptionsRequirement = await _policyRequirementQuery.GetAsync(userId.Value);
- if (sendOptionsRequirement.DisableHideEmail && send.HideEmail.GetValueOrDefault())
+ if (sendRequirement.DisableHideEmail && send.HideEmail.GetValueOrDefault())
{
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirementFactoryTests.cs
deleted file mode 100644
index 2304c0e9ae2e..000000000000
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/DisableSendPolicyRequirementFactoryTests.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
-using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-using Bit.Core.Test.AdminConsole.AutoFixture;
-using Bit.Test.Common.AutoFixture;
-using Bit.Test.Common.AutoFixture.Attributes;
-using Xunit;
-
-namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-
-[SutProviderCustomize]
-public class DisableSendPolicyRequirementFactoryTests
-{
- [Theory, BitAutoData]
- public void DisableSend_IsFalse_IfNoPolicies(SutProvider sutProvider)
- {
- var actual = sutProvider.Sut.Create([]);
-
- Assert.False(actual.DisableSend);
- }
-
- [Theory, BitAutoData]
- public void DisableSend_IsTrue_IfAnyDisableSendPolicies(
- [PolicyDetails(PolicyType.DisableSend)] PolicyDetails[] policies,
- SutProvider sutProvider
- )
- {
- var actual = sutProvider.Sut.Create(policies);
-
- Assert.True(actual.DisableSend);
- }
-}
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirementFactoryTests.cs
deleted file mode 100644
index af66d858ef46..000000000000
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendOptionsPolicyRequirementFactoryTests.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Bit.Core.AdminConsole.Enums;
-using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
-using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-using Bit.Core.Test.AdminConsole.AutoFixture;
-using Bit.Test.Common.AutoFixture;
-using Bit.Test.Common.AutoFixture.Attributes;
-using Xunit;
-
-namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-
-[SutProviderCustomize]
-public class SendOptionsPolicyRequirementFactoryTests
-{
- [Theory, BitAutoData]
- public void DisableHideEmail_IsFalse_IfNoPolicies(SutProvider sutProvider)
- {
- var actual = sutProvider.Sut.Create([]);
-
- Assert.False(actual.DisableHideEmail);
- }
-
- [Theory, BitAutoData]
- public void DisableHideEmail_IsFalse_IfNotConfigured(
- [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
- SutProvider sutProvider
- )
- {
- policies[0].SetDataModel(new SendOptionsPolicyData { DisableHideEmail = false });
- policies[1].SetDataModel(new SendOptionsPolicyData { DisableHideEmail = false });
-
- var actual = sutProvider.Sut.Create(policies);
-
- Assert.False(actual.DisableHideEmail);
- }
-
- [Theory, BitAutoData]
- public void DisableHideEmail_IsTrue_IfAnyConfigured(
- [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
- SutProvider sutProvider
- )
- {
- policies[0].SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true });
- policies[1].SetDataModel(new SendOptionsPolicyData { DisableHideEmail = false });
-
- var actual = sutProvider.Sut.Create(policies);
-
- Assert.True(actual.DisableHideEmail);
- }
-}
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
new file mode 100644
index 000000000000..8afbdc94e7d6
--- /dev/null
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
@@ -0,0 +1,85 @@
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
+using Bit.Core.Test.AdminConsole.AutoFixture;
+using Bit.Test.Common.AutoFixture;
+using Bit.Test.Common.AutoFixture.Attributes;
+using Xunit;
+
+namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
+
+[SutProviderCustomize]
+public class SendPolicyRequirementFactoryTests
+{
+ [Theory, BitAutoData]
+ public void DisableSend_IsFalse_IfNoPolicies(SutProvider sutProvider)
+ {
+ var actual = sutProvider.Sut.Create([]);
+
+ Assert.False(actual.DisableSend);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableSend_IsFalse_IfNotConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableSend = false });
+ policies[1].SetDataModel(new SendPolicyData { DisableSend = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisableSend);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableSend_IsTrue_IfAnyConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableSend = true });
+ policies[1].SetDataModel(new SendPolicyData { DisableSend = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisableSend);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableHideEmail_IsFalse_IfNoPolicies(SutProvider sutProvider)
+ {
+ var actual = sutProvider.Sut.Create([]);
+
+ Assert.False(actual.DisableHideEmail);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableHideEmail_IsFalse_IfNotConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableHideEmail = false });
+ policies[1].SetDataModel(new SendPolicyData { DisableHideEmail = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisableHideEmail);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableHideEmail_IsTrue_IfAnyConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableHideEmail = true });
+ policies[1].SetDataModel(new SendPolicyData { DisableHideEmail = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisableHideEmail);
+ }
+}
diff --git a/util/Migrator/DbScripts/2026-02-24_00_ConsolidateDisableSendIntoSendOptions.sql b/util/Migrator/DbScripts/2026-02-24_00_ConsolidateDisableSendIntoSendOptions.sql
new file mode 100644
index 000000000000..1fb9cc0823ff
--- /dev/null
+++ b/util/Migrator/DbScripts/2026-02-24_00_ConsolidateDisableSendIntoSendOptions.sql
@@ -0,0 +1,42 @@
+-- Consolidate DisableSend (type 6) policies into SendOptions (type 7) policies.
+-- This is the Phase 1 (initial) migration: it populates SendOptions rows with
+-- disableSend=true before the new code is deployed, without removing type 6 rows.
+-- Type 6 rows are intentionally left in place so that a rollback to the previous
+-- release continues to enforce the policy correctly.
+-- The DELETE of type 6 rows is a breaking change deferred to DbScripts_finalization.
+
+-- Step 1: For orgs that have DisableSend (type 6) enabled AND already have a
+-- SendOptions (type 7) row, merge disableSend=true into the existing JSON data.
+UPDATE [dbo].[Policy]
+SET [Data] = JSON_MODIFY(ISNULL([Data], '{}'), '$.disableSend', CAST(1 AS BIT)),
+ [RevisionDate] = GETUTCDATE()
+WHERE [Type] = 7
+ AND [OrganizationId] IN (
+ SELECT [OrganizationId]
+ FROM [dbo].[Policy]
+ WHERE [Type] = 6
+ AND [Enabled] = 1
+ );
+GO
+
+-- Step 2: For orgs that have DisableSend (type 6) enabled but NO SendOptions (type 7)
+-- row yet, insert a new enabled SendOptions row with disableSend=true.
+INSERT INTO [dbo].[Policy] ([Id], [OrganizationId], [Type], [Data], [Enabled], [CreationDate], [RevisionDate])
+SELECT
+ NEWID(),
+ ds.[OrganizationId],
+ 7,
+ '{"disableSend":true}',
+ 1,
+ GETUTCDATE(),
+ GETUTCDATE()
+FROM [dbo].[Policy] ds
+WHERE ds.[Type] = 6
+ AND ds.[Enabled] = 1
+ AND NOT EXISTS (
+ SELECT 1
+ FROM [dbo].[Policy] so
+ WHERE so.[OrganizationId] = ds.[OrganizationId]
+ AND so.[Type] = 7
+ );
+GO
diff --git a/util/Migrator/DbScripts_finalization/2026-02-FinalizationMigration.sql b/util/Migrator/DbScripts_finalization/2026-02-FinalizationMigration.sql
new file mode 100644
index 000000000000..84d52cdc3386
--- /dev/null
+++ b/util/Migrator/DbScripts_finalization/2026-02-FinalizationMigration.sql
@@ -0,0 +1,11 @@
+-- Remove all DisableSend (type 6) policy rows.
+-- These were consolidated into SendOptions (type 7) rows with disableSend=true
+-- in the Phase 1 migration (2026-02-24_00_ConsolidateDisableSendIntoSendOptions.sql).
+-- This finalization runs during the next release deployment once no rollback to the
+-- previous release is possible, making the removal of type 6 rows safe.
+
+-- Move this file to DbScripts/ as part of the next release.
+
+DELETE FROM [dbo].[Policy]
+WHERE [Type] = 6;
+GO
From aea6b31f5e0f236cecbed293c93bc6f5a628f82e Mon Sep 17 00:00:00 2001
From: John Harrington <84741727+harr1424@users.noreply.github.com>
Date: Wed, 25 Feb 2026 11:10:38 -0700
Subject: [PATCH 2/2] add Send policies related to access restrictions
---
.../Organizations/Policies/SendPolicyData.cs | 9 +
.../SendPolicyRequirement.cs | 31 ++-
.../Services/SendValidationService.cs | 54 +++-
.../SendPolicyRequirementFactoryTests.cs | 136 +++++++++
.../Services/SendValidationServiceTests.cs | 263 ++++++++++++++++++
5 files changed, 484 insertions(+), 9 deletions(-)
diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
index 7e4046b9e82f..859cd9939e8a 100644
--- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
+++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendPolicyData.cs
@@ -9,4 +9,13 @@ public class SendPolicyData : IPolicyDataModel
[Display(Name = "DisableHideEmail")]
public bool DisableHideEmail { get; set; }
+
+ [Display(Name = "DisableNoAuthSends")]
+ public bool DisableNoAuthSends { get; set; }
+
+ [Display(Name = "DisablePasswordSends")]
+ public bool DisablePasswordSends { get; set; }
+
+ [Display(Name = "DisableEmailVerifiedSends")]
+ public bool DisableEmailVerifiedSends { get; set; }
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
index 0aa973f8584e..99f6fde4efec 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirement.cs
@@ -18,6 +18,21 @@ public class SendPolicyRequirement : IPolicyRequirement
/// Indicates whether the org users are prohibited from hiding their email from the recipient of a Send.
///
public bool DisableHideEmail { get; init; }
+
+ ///
+ /// Indicates whether the org users are prohibited from creating or editing Sends that use no authorization.
+ ///
+ public bool DisableNoAuthSends { get; init; }
+
+ ///
+ /// Indicates whether the org users are prohibited from creating or editing Sends that use password authorization.
+ ///
+ public bool DisablePasswordSends { get; init; }
+
+ ///
+ /// Indicates whether the org users are prohibited from creating or editing Sends that use email verification.
+ ///
+ public bool DisableEmailVerifiedSends { get; init; }
}
public class SendPolicyRequirementFactory : BasePolicyRequirementFactory
@@ -30,10 +45,20 @@ public override SendPolicyRequirement Create(IEnumerable policyDe
.Select(p => p.GetDataModel())
.Aggregate(
new SendPolicyRequirement(),
- (result, data) => new SendPolicyRequirement
+ (result, data) =>
{
- DisableSend = result.DisableSend || data.DisableSend,
- DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail
+ var disableNoAuthSends = result.DisableNoAuthSends || data.DisableNoAuthSends;
+ var disablePasswordSends = result.DisablePasswordSends || data.DisablePasswordSends;
+ var disableEmailVerifiedSends = result.DisableEmailVerifiedSends || data.DisableEmailVerifiedSends;
+ return new SendPolicyRequirement
+ {
+ DisableSend = result.DisableSend || data.DisableSend
+ || (disableNoAuthSends && disablePasswordSends && disableEmailVerifiedSends),
+ DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail,
+ DisableNoAuthSends = disableNoAuthSends,
+ DisablePasswordSends = disablePasswordSends,
+ DisableEmailVerifiedSends = disableEmailVerifiedSends,
+ };
});
}
}
diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
index a951d6233367..b409d1fc1fce 100644
--- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
+++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs
@@ -13,6 +13,7 @@
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
+using Bit.Core.Tools.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Tools.Services;
@@ -74,13 +75,37 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send)
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
}
- if (send.HideEmail.GetValueOrDefault())
+ var sendOptionsPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId.Value, PolicyType.SendOptions);
+ var sendOptionsPolicyData = sendOptionsPolicies
+ .Select(p => CoreHelpers.LoadClassFromJsonData(p.PolicyData))
+ .Where(d => d != null)
+ .ToList();
+
+ if (sendOptionsPolicyData.Any(d => d.DisableSend))
{
- var sendOptionsPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId.Value, PolicyType.SendOptions);
- if (sendOptionsPolicies.Any(p => CoreHelpers.LoadClassFromJsonData(p.PolicyData)?.DisableHideEmail ?? false))
- {
- throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
- }
+ throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
+ }
+
+ if (send.HideEmail.GetValueOrDefault() && sendOptionsPolicyData.Any(d => d.DisableHideEmail))
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
+ }
+
+ var authType = send.AuthType ?? AuthType.None;
+
+ if (authType == AuthType.None && sendOptionsPolicyData.Any(d => d.DisableNoAuthSends))
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends without authentication.");
+ }
+
+ if (authType == AuthType.Password && sendOptionsPolicyData.Any(d => d.DisablePasswordSends))
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends that use password authentication.");
+ }
+
+ if (authType == AuthType.Email && sendOptionsPolicyData.Any(d => d.DisableEmailVerifiedSends))
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends that use email verification.");
}
}
@@ -102,6 +127,23 @@ public async Task ValidateUserCanSaveAsync_vNext(Guid? userId, Send send)
{
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
}
+
+ var authType = send.AuthType ?? AuthType.None;
+
+ if (sendRequirement.DisableNoAuthSends && authType == AuthType.None)
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends without authentication.");
+ }
+
+ if (sendRequirement.DisablePasswordSends && authType == AuthType.Password)
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends that use password authentication.");
+ }
+
+ if (sendRequirement.DisableEmailVerifiedSends && authType == AuthType.Email)
+ {
+ throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to create or edit Sends that use email verification.");
+ }
}
public async Task StorageRemainingForSendAsync(Send send)
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
index 8afbdc94e7d6..5e3c6b597bea 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendPolicyRequirementFactoryTests.cs
@@ -82,4 +82,140 @@ SutProvider sutProvider
Assert.True(actual.DisableHideEmail);
}
+
+ [Theory, BitAutoData]
+ public void DisableNoAuthSends_IsFalse_IfNoPolicies(SutProvider sutProvider)
+ {
+ var actual = sutProvider.Sut.Create([]);
+
+ Assert.False(actual.DisableNoAuthSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableNoAuthSends_IsFalse_IfNotConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableNoAuthSends = false });
+ policies[1].SetDataModel(new SendPolicyData { DisableNoAuthSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisableNoAuthSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableNoAuthSends_IsTrue_IfAnyConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableNoAuthSends = true });
+ policies[1].SetDataModel(new SendPolicyData { DisableNoAuthSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisableNoAuthSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisablePasswordSends_IsFalse_IfNoPolicies(SutProvider sutProvider)
+ {
+ var actual = sutProvider.Sut.Create([]);
+
+ Assert.False(actual.DisablePasswordSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisablePasswordSends_IsFalse_IfNotConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisablePasswordSends = false });
+ policies[1].SetDataModel(new SendPolicyData { DisablePasswordSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisablePasswordSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisablePasswordSends_IsTrue_IfAnyConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisablePasswordSends = true });
+ policies[1].SetDataModel(new SendPolicyData { DisablePasswordSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisablePasswordSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableEmailVerifiedSends_IsFalse_IfNoPolicies(SutProvider sutProvider)
+ {
+ var actual = sutProvider.Sut.Create([]);
+
+ Assert.False(actual.DisableEmailVerifiedSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableEmailVerifiedSends_IsFalse_IfNotConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableEmailVerifiedSends = false });
+ policies[1].SetDataModel(new SendPolicyData { DisableEmailVerifiedSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisableEmailVerifiedSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableEmailVerifiedSends_IsTrue_IfAnyConfigured(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableEmailVerifiedSends = true });
+ policies[1].SetDataModel(new SendPolicyData { DisableEmailVerifiedSends = false });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisableEmailVerifiedSends);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableSend_IsFalse_IfOnlyTwoAuthTypesDisabledAcrossOrgs(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableNoAuthSends = true });
+ policies[1].SetDataModel(new SendPolicyData { DisablePasswordSends = true });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.False(actual.DisableSend);
+ }
+
+ [Theory, BitAutoData]
+ public void DisableSend_IsTrue_IfAllThreeAuthTypesDisabledAcrossOrgs(
+ [PolicyDetails(PolicyType.SendOptions)] PolicyDetails[] policies,
+ SutProvider sutProvider
+ )
+ {
+ policies[0].SetDataModel(new SendPolicyData { DisableNoAuthSends = true, DisablePasswordSends = true });
+ policies[1].SetDataModel(new SendPolicyData { DisableEmailVerifiedSends = true });
+
+ var actual = sutProvider.Sut.Create(policies);
+
+ Assert.True(actual.DisableSend);
+ }
}
diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs
index 8adce1a29f84..dafe2d4cf10c 100644
--- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs
+++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs
@@ -1,12 +1,20 @@
using Bit.Core.AdminConsole.Entities;
+using Bit.Core.AdminConsole.Enums;
+using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
+using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
+using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Pricing.Premium;
+using Bit.Core.Context;
using Bit.Core.Entities;
+using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Services;
+using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
@@ -117,4 +125,259 @@ public async Task StorageRemainingForSendAsync_OrgSend_DoesNotCallPricingService
// Assert - should NOT call pricing service for org sends
await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan();
}
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisableNoAuthSends_ThrowsWhenAuthTypeIsNull(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = null;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisableNoAuthSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisableNoAuthSends_ThrowsWhenAuthTypeIsNone(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.None;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisableNoAuthSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisableNoAuthSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Password;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisableNoAuthSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisablePasswordSends_ThrowsWhenPasswordAuth(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Password;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisablePasswordSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisablePasswordSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Email;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisablePasswordSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisableEmailVerifiedSends_ThrowsWhenEmailAuth(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Email;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisableEmailVerifiedSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_vNext_DisableEmailVerifiedSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.None;
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(true);
+ sutProvider.GetDependency()
+ .GetAsync(userId)
+ .Returns(new SendPolicyRequirement { DisableEmailVerifiedSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
+
+ private static OrganizationUserPolicyDetails SendOptionsPolicyWith(SendPolicyData data) =>
+ new()
+ {
+ PolicyType = PolicyType.SendOptions,
+ PolicyEnabled = true,
+ PolicyData = CoreHelpers.ClassToJsonData(data),
+ };
+
+ private static void SetupLegacyPath(
+ Guid userId,
+ SutProvider sutProvider,
+ SendPolicyData policyData)
+ {
+ sutProvider.GetDependency()
+ .IsEnabled(FeatureFlagKeys.PolicyRequirements)
+ .Returns(false);
+ sutProvider.GetDependency().Organizations =
+ [new CurrentContextOrganization()];
+ sutProvider.GetDependency()
+ .AnyPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend)
+ .Returns(false);
+ sutProvider.GetDependency()
+ .GetPoliciesApplicableToUserAsync(userId, PolicyType.SendOptions)
+ .Returns([SendOptionsPolicyWith(policyData)]);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableSendOnSendOptions_Throws(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableSend = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableNoAuthSends_ThrowsWhenAuthTypeIsNull(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = null;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableNoAuthSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableNoAuthSends_ThrowsWhenAuthTypeIsNone(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.None;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableNoAuthSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableNoAuthSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Password;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableNoAuthSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisablePasswordSends_ThrowsWhenPasswordAuth(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Password;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisablePasswordSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisablePasswordSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Email;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisablePasswordSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableEmailVerifiedSends_ThrowsWhenEmailAuth(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.Email;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableEmailVerifiedSends = true });
+
+ await Assert.ThrowsAsync(
+ () => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send));
+ }
+
+ [Theory, BitAutoData]
+ public async Task ValidateUserCanSaveAsync_Legacy_DisableEmailVerifiedSends_AllowsOtherAuthTypes(
+ Guid userId,
+ Send send,
+ SutProvider sutProvider)
+ {
+ send.AuthType = AuthType.None;
+ SetupLegacyPath(userId, sutProvider, new SendPolicyData { DisableEmailVerifiedSends = true });
+
+ // Should not throw
+ await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send);
+ }
}