From 2bb7c17cce220eff780d2a41384a01b83ff1ca7c Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:13:23 -0700 Subject: [PATCH 01/40] initial send controls --- src/Core/AdminConsole/Enums/PolicyType.cs | 9 + .../Policies/SendControlsPolicyData.cs | 12 + .../SendControlsPolicyRequirement.cs | 39 + .../PolicyServiceCollectionExtensions.cs | 4 + .../DisableSendSyncPolicyValidator.cs | 54 + .../SendControlsSyncPolicyValidator.cs | 75 + .../SendOptionsSyncPolicyValidator.cs | 58 + src/Core/Constants.cs | 1 + .../Services/SendValidationService.cs | 32 + ...26-02-28_00_CreateSendControlsPolicies.sql | 55 + ...000_CreateSendControlsPolicies.Designer.cs | 3568 ++++++++++++++++ ...260228000000_CreateSendControlsPolicies.cs | 64 + ...000_CreateSendControlsPolicies.Designer.cs | 3574 +++++++++++++++++ ...260228000000_CreateSendControlsPolicies.cs | 52 + ...000_CreateSendControlsPolicies.Designer.cs | 3557 ++++++++++++++++ ...260228000000_CreateSendControlsPolicies.cs | 76 + 16 files changed, 11230 insertions(+) create mode 100644 src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs create mode 100644 util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql create mode 100644 util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs create mode 100644 util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs create mode 100644 util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs diff --git a/src/Core/AdminConsole/Enums/PolicyType.cs b/src/Core/AdminConsole/Enums/PolicyType.cs index bd6daf7cdffa..ad61782521ab 100644 --- a/src/Core/AdminConsole/Enums/PolicyType.cs +++ b/src/Core/AdminConsole/Enums/PolicyType.cs @@ -8,7 +8,11 @@ public enum PolicyType : byte SingleOrg = 3, RequireSso = 4, OrganizationDataOwnership = 5, + // Deprecated: superseded by SendControls (20) when pm-31885-send-controls flag is active. + // Do not add [Obsolete] until the flag is retired. DisableSend = 6, + // Deprecated: superseded by SendControls (20) when pm-31885-send-controls flag is active. + // Do not add [Obsolete] until the flag is retired. SendOptions = 7, ResetPassword = 8, MaximumVaultTimeout = 9, @@ -22,6 +26,10 @@ public enum PolicyType : byte AutotypeDefaultSetting = 17, AutomaticUserConfirmation = 18, BlockClaimedDomainAccountCreation = 19, + /// + /// Supersedes DisableSend (6) and SendOptions (7) when the pm-31885-send-controls feature flag is active. + /// + SendControls = 20, } public static class PolicyTypeExtensions @@ -54,6 +62,7 @@ public static string GetName(this PolicyType type) PolicyType.AutotypeDefaultSetting => "Autotype default setting", PolicyType.AutomaticUserConfirmation => "Automatically confirm invited users", PolicyType.BlockClaimedDomainAccountCreation => "Block account creation for claimed domains", + PolicyType.SendControls => "Send controls", }; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs new file mode 100644 index 000000000000..4a4027aa4c52 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +public class SendControlsPolicyData : 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/SendControlsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs new file mode 100644 index 000000000000..de0646ff1ba2 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.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 Controls policy. +/// +public class SendControlsPolicyRequirement : 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; } + + /// + /// Indicates whether the user is prohibited from hiding their email from the recipient of a Send. + /// + public bool DisableHideEmail { get; init; } +} + +public class SendControlsPolicyRequirementFactory : BasePolicyRequirementFactory +{ + public override PolicyType PolicyType => PolicyType.SendControls; + + public override SendControlsPolicyRequirement Create(IEnumerable policyDetails) + { + return policyDetails + .Select(p => p.GetDataModel()) + .Aggregate( + new SendControlsPolicyRequirement(), + (result, data) => new SendControlsPolicyRequirement + { + 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..c5a0054cf05a 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -63,6 +63,9 @@ private static void AddPolicyUpdateEvents(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void AddPolicyRequirements(this IServiceCollection services) @@ -76,5 +79,6 @@ private static void AddPolicyRequirements(this IServiceCollection services) services.AddScoped, MasterPasswordPolicyRequirementFactory>(); services.AddScoped, SingleOrganizationPolicyRequirementFactory>(); services.AddScoped, AutomaticUserConfirmationPolicyRequirementFactory>(); + services.AddScoped, SendControlsPolicyRequirementFactory>(); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs new file mode 100644 index 000000000000..32d085b9da0f --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs @@ -0,0 +1,54 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +/// +/// Syncs changes to the DisableSend policy into the SendControls policy row. +/// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls +/// always stays current for when the flag is eventually enabled. +/// +public class DisableSendSyncPolicyValidator(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +{ + public PolicyType Type => PolicyType.DisableSend; + + public async Task ExecutePostUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy postUpsertedPolicyState, + Policy? previousPolicyState) + { + var policyUpdate = policyRequest.PolicyUpdate; + + var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + policyUpdate.OrganizationId, PolicyType.SendControls); + + var data = sendControlsPolicy != null + ? CoreHelpers.LoadClassFromJsonData(sendControlsPolicy.Data) ?? new SendControlsPolicyData() + : new SendControlsPolicyData(); + + data.DisableSend = postUpsertedPolicyState.Enabled; + + var policy = sendControlsPolicy ?? new Policy + { + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; + + if (sendControlsPolicy == null) + { + policy.SetNewId(); + } + + policy.Enabled = data.DisableSend || data.DisableHideEmail; + policy.SetDataModel(data); + + await policyRepository.UpsertAsync(policy); + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs new file mode 100644 index 000000000000..405bc564a176 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs @@ -0,0 +1,75 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Services; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +/// +/// When the pm-31885-send-controls flag is active, syncs changes to the SendControls policy +/// back into the legacy DisableSend and SendOptions policy rows, enabling safe rollback. +/// +public class SendControlsSyncPolicyValidator( + IPolicyRepository policyRepository, + IFeatureService featureService) : IOnPolicyPostUpdateEvent +{ + public PolicyType Type => PolicyType.SendControls; + + public async Task ExecutePostUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy postUpsertedPolicyState, + Policy? previousPolicyState) + { + if (!featureService.IsEnabled(FeatureFlagKeys.SendControls)) + { + return; + } + + var data = CoreHelpers.LoadClassFromJsonData(postUpsertedPolicyState.Data) + ?? new SendControlsPolicyData(); + + await UpsertLegacyPolicyAsync( + policyRequest.PolicyUpdate.OrganizationId, + PolicyType.DisableSend, + enabled: postUpsertedPolicyState.Enabled && data.DisableSend, + policyData: null); + + var sendOptionsData = new SendOptionsPolicyData { DisableHideEmail = data.DisableHideEmail }; + await UpsertLegacyPolicyAsync( + policyRequest.PolicyUpdate.OrganizationId, + PolicyType.SendOptions, + enabled: postUpsertedPolicyState.Enabled && data.DisableHideEmail, + policyData: CoreHelpers.ClassToJsonData(sendOptionsData)); + } + + private async Task UpsertLegacyPolicyAsync( + Guid organizationId, + PolicyType type, + bool enabled, + string? policyData) + { + var existing = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, type); + + var policy = existing ?? new Policy + { + OrganizationId = organizationId, + Type = type, + }; + + if (existing == null) + { + policy.SetNewId(); + } + + policy.Enabled = enabled; + policy.Data = policyData; + + await policyRepository.UpsertAsync(policy); + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs new file mode 100644 index 000000000000..1df362d30688 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs @@ -0,0 +1,58 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +/// +/// Syncs changes to the SendOptions policy into the SendControls policy row. +/// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls +/// always stays current for when the flag is eventually enabled. +/// +public class SendOptionsSyncPolicyValidator(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +{ + public PolicyType Type => PolicyType.SendOptions; + + public async Task ExecutePostUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy postUpsertedPolicyState, + Policy? previousPolicyState) + { + var policyUpdate = policyRequest.PolicyUpdate; + + var parsedSendOptions = postUpsertedPolicyState.Enabled + ? CoreHelpers.LoadClassFromJsonData(postUpsertedPolicyState.Data) + : null; + + var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + policyUpdate.OrganizationId, PolicyType.SendControls); + + var data = sendControlsPolicy != null + ? CoreHelpers.LoadClassFromJsonData(sendControlsPolicy.Data) ?? new SendControlsPolicyData() + : new SendControlsPolicyData(); + + data.DisableHideEmail = parsedSendOptions?.DisableHideEmail ?? false; + + var policy = sendControlsPolicy ?? new Policy + { + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; + + if (sendControlsPolicy == null) + { + policy.SetNewId(); + } + + policy.Enabled = data.DisableSend || data.DisableHideEmail; + policy.SetDataModel(data); + + await policyRepository.UpsertAsync(policy); + } +} diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index a0e8482d668b..9e492ca45435 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -244,6 +244,7 @@ public static class FeatureFlagKeys public const string ChromiumImporterWithABE = "pm-25855-chromium-importer-abe"; public const string SendUIRefresh = "pm-28175-send-ui-refresh"; public const string SendEmailOTP = "pm-19051-send-email-verification"; + public const string SendControls = "pm-31885-send-controls"; /* Vault Team */ public const string CipherKeyEncryption = "cipher-key-encryption"; diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index bd987bb396bc..afa0caac14e2 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -56,6 +56,12 @@ public SendValidationService( public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) { + if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) + { + await ValidateUserCanSaveAsync_SendControls(userId, send); + return; + } + if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) { await ValidateUserCanSaveAsync_vNext(userId, send); @@ -84,6 +90,32 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) } } + private async Task ValidateUserCanSaveAsync_SendControls(Guid? userId, Send send) + { + if (!userId.HasValue || (!_currentContext.Organizations?.Any() ?? true)) + { + return; + } + + var sendControlsPolicies = await _policyService.GetPoliciesApplicableToUserAsync( + userId.Value, PolicyType.SendControls); + + if (sendControlsPolicies.Any(p => + CoreHelpers.LoadClassFromJsonData(p.PolicyData)?.DisableSend ?? false)) + { + throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send."); + } + + if (send.HideEmail.GetValueOrDefault()) + { + if (sendControlsPolicies.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."); + } + } + } + public async Task ValidateUserCanSaveAsync_vNext(Guid? userId, Send send) { if (!userId.HasValue) diff --git a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql new file mode 100644 index 000000000000..38a42451d4d7 --- /dev/null +++ b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql @@ -0,0 +1,55 @@ +-- Migrate existing DisableSend (6) and SendOptions (7) policies into new SendControls (20) +-- EDD-compatible: only inserts new rows, never modifies existing data + +DECLARE @SendControlsType TINYINT = 20; +DECLARE @DisableSendType TINYINT = 6; +DECLARE @SendOptionsType TINYINT = 7; +DECLARE @BatchSize INT = 2000; +DECLARE @RowsAffected INT = 1; + +WHILE @RowsAffected > 0 +BEGIN + INSERT INTO [dbo].[Policy] ( + [Id], [OrganizationId], [Type], [Enabled], [Data], [CreationDate], [RevisionDate] + ) + SELECT TOP (@BatchSize) + NEWID(), + combined.OrganizationId, + @SendControlsType, + -- Policy is enabled if either old policy was enabled + CASE WHEN ISNULL(combined.DisableSendEnabled, 0) = 1 + OR ISNULL(combined.SendOptionsEnabled, 0) = 1 + THEN 1 ELSE 0 END, + -- Build JSON: use ISJSON guard for SendOptions.Data + N'{"disableSend":' + + CASE WHEN ISNULL(combined.DisableSendEnabled, 0) = 1 + THEN N'true' ELSE N'false' END + + N',"disableHideEmail":' + + CASE WHEN combined.SendOptionsData IS NOT NULL + AND ISJSON(combined.SendOptionsData) = 1 + AND JSON_VALUE(combined.SendOptionsData, '$.disableHideEmail') = 'true' + THEN N'true' ELSE N'false' END + + N'}', + GETUTCDATE(), + GETUTCDATE() + FROM ( + SELECT + COALESCE(ds.OrganizationId, so.OrganizationId) AS OrganizationId, + ds.Enabled AS DisableSendEnabled, + so.Enabled AS SendOptionsEnabled, + so.Data AS SendOptionsData + FROM [dbo].[Policy] ds + FULL OUTER JOIN [dbo].[Policy] so + ON ds.OrganizationId = so.OrganizationId + AND so.Type = @SendOptionsType + WHERE (ds.Type = @DisableSendType OR so.Type = @SendOptionsType) + ) combined + -- Skip orgs that already have a SendControls row + WHERE NOT EXISTS ( + SELECT 1 FROM [dbo].[Policy] sc + WHERE sc.OrganizationId = combined.OrganizationId + AND sc.Type = @SendControlsType + ); + + SET @RowsAffected = @@ROWCOUNT; +END diff --git a/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs new file mode 100644 index 000000000000..3c9419eecbc4 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs @@ -0,0 +1,3568 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260228000000_CreateSendControlsPolicies")] + partial class CreateSendControlsPolicies + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("tinyint(1)"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseMyItems") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePhishingBlocker") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AmountOff") + .HasColumnType("bigint"); + + b.Property("AudienceType") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Currency") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Duration") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("DurationInMonths") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("PercentOff") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("StripeCouponId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("StripeProductIds") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("StripeCouponId") + .IsUnique(); + + b.HasIndex("StartDate", "EndDate") + .HasDatabaseName("IX_SubscriptionDiscount_DateRange") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SubscriptionDiscount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("ApplicationCount") + .HasColumnType("int"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalApplicationCount") + .HasColumnType("int"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalMemberCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordCount") + .HasColumnType("int"); + + b.Property("MemberAtRiskCount") + .HasColumnType("int"); + + b.Property("MemberCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("PasswordCount") + .HasColumnType("int"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("AuthType") + .HasColumnType("tinyint unsigned"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SecurityState") + .HasColumnType("longtext"); + + b.Property("SecurityVersion") + .HasColumnType("int"); + + b.Property("SignedPublicKey") + .HasColumnType("longtext"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SignatureAlgorithm") + .HasColumnType("tinyint unsigned"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("EditorServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VersionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Archives") + .HasColumnType("longtext"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs new file mode 100644 index 000000000000..1f3b0ae2999e --- /dev/null +++ b/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class CreateSendControlsPolicies : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + -- Insert for orgs that have SendOptions (with or without DisableSend) + INSERT INTO `Policy` (`Id`, `OrganizationId`, `Type`, `Enabled`, `Data`, `CreationDate`, `RevisionDate`) + SELECT UUID(), + COALESCE(ds.`OrganizationId`, so.`OrganizationId`), + 20, + IF(IFNULL(ds.`Enabled`, 0) = 1 OR IFNULL(so.`Enabled`, 0) = 1, 1, 0), + CONCAT('{""disableSend"":', + IF(IFNULL(ds.`Enabled`, 0) = 1, 'true', 'false'), + ',""disableHideEmail"":', + IF(so.`Data` IS NOT NULL AND JSON_VALID(so.`Data`) + AND JSON_EXTRACT(so.`Data`, '$.disableHideEmail') = true, + 'true', 'false'), + '}'), + UTC_TIMESTAMP(), + UTC_TIMESTAMP() + FROM (SELECT `OrganizationId`, `Enabled`, `Data` FROM `Policy` WHERE `Type` = 7) so + LEFT JOIN (SELECT `OrganizationId`, `Enabled` FROM `Policy` WHERE `Type` = 6) ds + ON ds.`OrganizationId` = so.`OrganizationId` + WHERE NOT EXISTS ( + SELECT 1 FROM `Policy` sc + WHERE sc.`OrganizationId` = COALESCE(ds.`OrganizationId`, so.`OrganizationId`) + AND sc.`Type` = 20 + ); + + -- Insert for orgs that have DisableSend ONLY (no SendOptions) + INSERT INTO `Policy` (`Id`, `OrganizationId`, `Type`, `Enabled`, `Data`, `CreationDate`, `RevisionDate`) + SELECT UUID(), + ds.`OrganizationId`, + 20, + ds.`Enabled`, + CONCAT('{""disableSend"":', IF(ds.`Enabled` = 1, 'true', 'false'), ',""disableHideEmail"":false}'), + UTC_TIMESTAMP(), + UTC_TIMESTAMP() + FROM (SELECT `OrganizationId`, `Enabled` FROM `Policy` WHERE `Type` = 6) ds + WHERE ds.`OrganizationId` NOT IN (SELECT `OrganizationId` FROM `Policy` WHERE `Type` = 7) + AND NOT EXISTS ( + SELECT 1 FROM `Policy` sc + WHERE sc.`OrganizationId` = ds.`OrganizationId` + AND sc.`Type` = 20 + ); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DELETE FROM `Policy` WHERE `Type` = 20; + "); + } +} diff --git a/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs new file mode 100644 index 000000000000..ca57dc806bc8 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs @@ -0,0 +1,3574 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260228000000_CreateSendControlsPolicies")] + partial class CreateSendControlsPolicies + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("boolean"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseMyItems") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePhishingBlocker") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AmountOff") + .HasColumnType("bigint"); + + b.Property("AudienceType") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Currency") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Duration") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("DurationInMonths") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PercentOff") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StripeCouponId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StripeProductIds") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("StripeCouponId") + .IsUnique(); + + b.HasIndex("StartDate", "EndDate") + .HasDatabaseName("IX_SubscriptionDiscount_DateRange") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SubscriptionDiscount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("ApplicationCount") + .HasColumnType("integer"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalApplicationCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordCount") + .HasColumnType("integer"); + + b.Property("MemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("MemberCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("PasswordCount") + .HasColumnType("integer"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("AuthType") + .HasColumnType("smallint"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SecurityState") + .HasColumnType("text"); + + b.Property("SecurityVersion") + .HasColumnType("integer"); + + b.Property("SignedPublicKey") + .HasColumnType("text"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SignatureAlgorithm") + .HasColumnType("smallint"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("EditorServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Archives") + .HasColumnType("text"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs new file mode 100644 index 000000000000..a1fe93cdc976 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class CreateSendControlsPolicies : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") + SELECT gen_random_uuid(), + COALESCE(ds.""OrganizationId"", so.""OrganizationId""), + 20, + (COALESCE(ds.""Enabled"", false) OR COALESCE(so.""Enabled"", false)), + jsonb_build_object( + 'disableSend', COALESCE(ds.""Enabled"", false), + 'disableHideEmail', + CASE WHEN so.""Data"" IS NOT NULL + AND so.""Data"" ~ '^\{.*\}$' + THEN COALESCE((so.""Data""::jsonb ->> 'disableHideEmail')::boolean, false) + ELSE false END + )::text, + NOW() AT TIME ZONE 'UTC', + NOW() AT TIME ZONE 'UTC' + FROM ( + SELECT ds2.""OrganizationId"", ds2.""Enabled"" + FROM ""Policy"" ds2 WHERE ds2.""Type"" = 6 + ) ds + FULL OUTER JOIN ( + SELECT so2.""OrganizationId"", so2.""Enabled"", so2.""Data"" + FROM ""Policy"" so2 WHERE so2.""Type"" = 7 + ) so ON ds.""OrganizationId"" = so.""OrganizationId"" + WHERE NOT EXISTS ( + SELECT 1 FROM ""Policy"" sc + WHERE sc.""OrganizationId"" = COALESCE(ds.""OrganizationId"", so.""OrganizationId"") + AND sc.""Type"" = 20 + ); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DELETE FROM ""Policy"" WHERE ""Type"" = 20; + "); + } +} diff --git a/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs new file mode 100644 index 000000000000..a716a8c55c8f --- /dev/null +++ b/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs @@ -0,0 +1,3557 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260228000000_CreateSendControlsPolicies")] + partial class CreateSendControlsPolicies + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("SyncSeats") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseMyItems") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePhishingBlocker") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AmountOff") + .HasColumnType("INTEGER"); + + b.Property("AudienceType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Duration") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("DurationInMonths") + .HasColumnType("INTEGER"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PercentOff") + .HasPrecision(5, 2) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("StripeCouponId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("StripeProductIds") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StripeCouponId") + .IsUnique(); + + b.HasIndex("StartDate", "EndDate") + .HasDatabaseName("IX_SubscriptionDiscount_DateRange") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SubscriptionDiscount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordCount") + .HasColumnType("INTEGER"); + + b.Property("MemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("MemberCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("PasswordCount") + .HasColumnType("INTEGER"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("AuthType") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .HasColumnType("INTEGER"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("EditorServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VersionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Archives") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs new file mode 100644 index 000000000000..b319bbcae75c --- /dev/null +++ b/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class CreateSendControlsPolicies : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // SQLite does not support FULL OUTER JOIN; use two separate inserts. + // SQLite 3.38+ has json_valid() and json_extract(). + + // Insert for orgs that have SendOptions (with or without DisableSend) + migrationBuilder.Sql(@" + INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") + SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || + substr(lower(hex(randomblob(2))),2) || '-' || + substr('89ab',abs(random()) % 4 + 1, 1) || + substr(lower(hex(randomblob(2))),2) || '-' || lower(hex(randomblob(6))), + COALESCE(ds.""OrganizationId"", so.""OrganizationId""), + 20, + CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 OR IFNULL(so.""Enabled"", 0) = 1 THEN 1 ELSE 0 END, + '{""disableSend"":' || + CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 THEN 'true' ELSE 'false' END || + ',""disableHideEmail"":' || + CASE WHEN so.""Data"" IS NOT NULL + AND json_valid(so.""Data"") = 1 + AND json_extract(so.""Data"", '$.disableHideEmail') = 1 + THEN 'true' ELSE 'false' END || + '}', + datetime('now'), + datetime('now') + FROM (SELECT ""OrganizationId"", ""Enabled"", ""Data"" FROM ""Policy"" WHERE ""Type"" = 7) so + LEFT JOIN (SELECT ""OrganizationId"", ""Enabled"" FROM ""Policy"" WHERE ""Type"" = 6) ds + ON ds.""OrganizationId"" = so.""OrganizationId"" + WHERE NOT EXISTS ( + SELECT 1 FROM ""Policy"" sc + WHERE sc.""OrganizationId"" = COALESCE(ds.""OrganizationId"", so.""OrganizationId"") + AND sc.""Type"" = 20 + ); + "); + + // Insert for orgs that have DisableSend ONLY (no SendOptions) + migrationBuilder.Sql(@" + INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") + SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || + substr(lower(hex(randomblob(2))),2) || '-' || + substr('89ab',abs(random()) % 4 + 1, 1) || + substr(lower(hex(randomblob(2))),2) || '-' || lower(hex(randomblob(6))), + ds.""OrganizationId"", + 20, + ds.""Enabled"", + '{""disableSend"":' || CASE WHEN ds.""Enabled"" = 1 THEN 'true' ELSE 'false' END || ',""disableHideEmail"":false}', + datetime('now'), + datetime('now') + FROM (SELECT ""OrganizationId"", ""Enabled"" FROM ""Policy"" WHERE ""Type"" = 6) ds + WHERE ds.""OrganizationId"" NOT IN (SELECT ""OrganizationId"" FROM ""Policy"" WHERE ""Type"" = 7) + AND NOT EXISTS ( + SELECT 1 FROM ""Policy"" sc + WHERE sc.""OrganizationId"" = ds.""OrganizationId"" + AND sc.""Type"" = 20 + ); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + DELETE FROM ""Policy"" WHERE ""Type"" = 20; + "); + } +} From 6f51c42f15c265099e9d8ae6eb96946bff7c8ab3 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:46:12 -0700 Subject: [PATCH 02/40] update vNext methods and add test coverage for policy validators --- .../Policies/SendControlsPolicyData.cs | 1 - .../SendControlsPolicyRequirement.cs | 1 + .../PolicyServiceCollectionExtensions.cs | 2 +- .../Services/SendValidationService.cs | 25 +- ...ndControlsPolicyRequirementFactoryTests.cs | 99 ++++++++ .../DisableSendSyncPolicyValidatorTests.cs | 94 ++++++++ .../SendControlsSyncPolicyValidatorTests.cs | 164 ++++++++++++++ .../SendOptionsSyncPolicyValidatorTests.cs | 98 ++++++++ .../Services/SendValidationServiceTests.cs | 213 +++++++++++++++++- 9 files changed, 688 insertions(+), 9 deletions(-) create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirementFactoryTests.cs create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs index 4a4027aa4c52..42d55aa40c4e 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs @@ -6,7 +6,6 @@ public class SendControlsPolicyData : 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/SendControlsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs index de0646ff1ba2..229a39028c86 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs @@ -5,6 +5,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements /// /// Policy requirements for the Send Controls policy. +/// Supersedes DisableSend and SendOptions when the pm-31885-send-controls feature flag is active. /// public class SendControlsPolicyRequirement : IPolicyRequirement { diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index c5a0054cf05a..12bdd80d2e17 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -72,6 +72,7 @@ private static void AddPolicyRequirements(this IServiceCollection services) { services.AddScoped, DisableSendPolicyRequirementFactory>(); services.AddScoped, SendOptionsPolicyRequirementFactory>(); + services.AddScoped, SendControlsPolicyRequirementFactory>(); services.AddScoped, ResetPasswordPolicyRequirementFactory>(); services.AddScoped, OrganizationDataOwnershipPolicyRequirementFactory>(); services.AddScoped, RequireSsoPolicyRequirementFactory>(); @@ -79,6 +80,5 @@ private static void AddPolicyRequirements(this IServiceCollection services) services.AddScoped, MasterPasswordPolicyRequirementFactory>(); services.AddScoped, SingleOrganizationPolicyRequirementFactory>(); services.AddScoped, AutomaticUserConfirmationPolicyRequirementFactory>(); - services.AddScoped, SendControlsPolicyRequirementFactory>(); } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index afa0caac14e2..f4d37d171886 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -56,15 +56,15 @@ public SendValidationService( public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) { - if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) + if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) { - await ValidateUserCanSaveAsync_SendControls(userId, send); + await ValidateUserCanSaveAsync_vNext(userId, send); return; } - if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) + if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) { - await ValidateUserCanSaveAsync_vNext(userId, send); + await ValidateUserCanSaveAsync_SendControls(userId, send); return; } @@ -123,6 +123,23 @@ public async Task ValidateUserCanSaveAsync_vNext(Guid? userId, Send send) return; } + if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) + { + var sendControlsRequirement = await _policyRequirementQuery.GetAsync(userId.Value); + + if (sendControlsRequirement.DisableSend) + { + throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send."); + } + + if (sendControlsRequirement.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."); + } + + return; + } + var disableSendRequirement = await _policyRequirementQuery.GetAsync(userId.Value); if (disableSendRequirement.DisableSend) { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirementFactoryTests.cs new file mode 100644 index 000000000000..c717aa2cf0ed --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirementFactoryTests.cs @@ -0,0 +1,99 @@ +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 SendControlsPolicyRequirementFactoryTests +{ + [Theory, BitAutoData] + public void DisableSend_IsFalse_IfNoPolicies(SutProvider sutProvider) + { + var actual = sutProvider.Sut.Create([]); + + Assert.False(actual.DisableSend); + } + + [Theory, BitAutoData] + public void DisableSend_IsFalse_WhenNotConfigured( + [PolicyDetails(PolicyType.SendControls)] PolicyDetails[] policies, + SutProvider sutProvider) + { + foreach (var policy in policies) + { + policy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false }); + } + + var actual = sutProvider.Sut.Create(policies); + + Assert.False(actual.DisableSend); + } + + [Theory, BitAutoData] + public void DisableSend_IsTrue_IfAnyPolicyHasDisableSend( + [PolicyDetails(PolicyType.SendControls)] PolicyDetails[] policies, + SutProvider sutProvider) + { + policies[0].SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); + policies[1].SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = 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_WhenNotConfigured( + [PolicyDetails(PolicyType.SendControls)] PolicyDetails[] policies, + SutProvider sutProvider) + { + foreach (var policy in policies) + { + policy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false }); + } + + var actual = sutProvider.Sut.Create(policies); + + Assert.False(actual.DisableHideEmail); + } + + [Theory, BitAutoData] + public void DisableHideEmail_IsTrue_IfAnyPolicyHasDisableHideEmail( + [PolicyDetails(PolicyType.SendControls)] PolicyDetails[] policies, + SutProvider sutProvider) + { + policies[0].SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); + policies[1].SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false }); + + var actual = sutProvider.Sut.Create(policies); + + Assert.True(actual.DisableHideEmail); + } + + [Theory, BitAutoData] + public void BothFields_AreOrAggregatedAcrossMultiplePolicies( + [PolicyDetails(PolicyType.SendControls)] PolicyDetails[] policies, + SutProvider sutProvider) + { + policies[0].SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); + policies[1].SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); + + var actual = sutProvider.Sut.Create(policies); + + Assert.True(actual.DisableSend); + Assert.True(actual.DisableHideEmail); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs new file mode 100644 index 000000000000..e93ad492c4f8 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs @@ -0,0 +1,94 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +[SutProviderCustomize] +public class DisableSendSyncPolicyValidatorTests +{ + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( + [PolicyUpdate(PolicyType.DisableSend, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.DisableSend, enabled: true)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns((Policy?)null); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.SendControls && + p.Enabled == true && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true))); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPolicy( + [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, + [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(existingSendControlsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Id == existingSendControlsPolicy.Id && + p.Enabled == false && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == false))); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenEitherFieldIsTrue( + [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, + [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; + // DisableSend is being turned off, but DisableHideEmail is still true + existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(existingSendControlsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Enabled == true)); // stays enabled because DisableHideEmail is still true + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs new file mode 100644 index 000000000000..af2e4f05eb2a --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs @@ -0,0 +1,164 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +[SutProviderCustomize] +public class SendControlsSyncPolicyValidatorTests +{ + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_DoesNothing_WhenFlagDisabled( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.SendControls) + .Returns(false); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .DidNotReceive() + .UpsertAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisableSendPolicy( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.SendControls) + .Returns(true); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .Returns((Policy?)null); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.DisableSend && + p.Enabled == true)); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableHideEmail_ToLegacySendOptionsPolicy( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.SendControls) + .Returns(true); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .Returns((Policy?)null); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.SendOptions && + p.Enabled == true && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_DisablesLegacyPolicies_WhenSendControlsPolicyDisabled( + [PolicyUpdate(PolicyType.SendControls, enabled: false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: false)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.SendControls) + .Returns(true); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .Returns((Policy?)null); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Type == PolicyType.DisableSend && + p.Enabled == false)); + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Type == PolicyType.SendOptions && + p.Enabled == false)); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingLegacyPolicies( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.SendControls) + .Returns(true); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Id == existingDisableSendPolicy.Id && + p.Enabled == true)); + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Id == existingSendOptionsPolicy.Id && + p.Enabled == true)); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs new file mode 100644 index 000000000000..0f03b02fdac5 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs @@ -0,0 +1,98 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; + +[SutProviderCustomize] +public class SendOptionsSyncPolicyValidatorTests +{ + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( + [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns((Policy?)null); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.SendControls && + p.Enabled == true && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_ClearsDisableHideEmail_WhenPolicyDisabled( + [PolicyUpdate(PolicyType.SendOptions, enabled: false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendOptions, enabled: false)] Policy postUpsertedPolicy, + [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(existingSendControlsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Enabled == false && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == false))); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPolicy( + [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.SendControls, enabled: false)] Policy existingSendControlsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(existingSendControlsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Id == existingSendControlsPolicy.Id && + p.Enabled == true && + (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); + } +} diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index 8adce1a29f84..c4e04530c6e9 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -1,12 +1,21 @@ 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.Exceptions; +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; @@ -69,7 +78,7 @@ public async Task StorageRemainingForSendAsync_IndividualPremium_DoesNotCallPric // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert - should NOT call pricing service for individual premium users + // Assert await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } @@ -93,7 +102,7 @@ public async Task StorageRemainingForSendAsync_SelfHosted_DoesNotCallPricingServ // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert - should NOT call pricing service for self-hosted + // Assert await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } @@ -114,7 +123,205 @@ public async Task StorageRemainingForSendAsync_OrgSend_DoesNotCallPricingService // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert - should NOT call pricing service for org sends + // Assert await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_Legacy_ThrowsWhenDisableSendPolicyApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(false); + sutProvider.GetDependency().Organizations + .Returns([new CurrentContextOrganization()]); + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_Legacy_ThrowsWhenSendOptionsDisableHideEmailApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = true; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(false); + sutProvider.GetDependency().Organizations + .Returns([new CurrentContextOrganization()]); + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend) + .Returns(false); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(userId, PolicyType.SendOptions) + .Returns([new OrganizationUserPolicyDetails + { + PolicyData = CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true }) + }]); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_Legacy_NoThrowWhenNoOrganizations( + SutProvider sutProvider, + Guid userId, + Send send) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(false); + sutProvider.GetDependency().Organizations.Returns([]); + + await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); + + await sutProvider.GetDependency() + .DidNotReceive() + .AnyPoliciesApplicableToUserAsync(Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_SendControls_ThrowsWhenDisableSendPolicyApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency().Organizations + .Returns([new CurrentContextOrganization()]); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(userId, PolicyType.SendControls) + .Returns([new OrganizationUserPolicyDetails + { + PolicyData = CoreHelpers.ClassToJsonData(new SendControlsPolicyData { DisableSend = true }) + }]); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_SendControls_ThrowsWhenDisableHideEmailApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = true; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency().Organizations + .Returns([new CurrentContextOrganization()]); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(userId, PolicyType.SendControls) + .Returns([new OrganizationUserPolicyDetails + { + PolicyData = CoreHelpers.ClassToJsonData(new SendControlsPolicyData { DisableHideEmail = true }) + }]); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_SendControls_NoThrowWhenNoPoliciesApply( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = true; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency().Organizations + .Returns([new CurrentContextOrganization()]); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(userId, PolicyType.SendControls) + .Returns([]); + + await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_VNext_ThrowsWhenSendControlsDisableSendApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = true, DisableHideEmail = false }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_VNext_ThrowsWhenSendControlsDisableHideEmailApplies( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = true; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = true }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_VNext_UsesLegacyDisableSendRequirement_WhenSendControlsFlagOff( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(false); + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = true }); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_VNext_UsesPolicyRequirementQuery_WhenPolicyRequirementsFlagOn( + SutProvider sutProvider, + Guid userId, + Send send) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); + + await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); + + // Must use policyRequirementQuery, not IPolicyService + await sutProvider.GetDependency() + .Received(1) + .GetAsync(userId); + await sutProvider.GetDependency() + .DidNotReceive() + .GetPoliciesApplicableToUserAsync(Arg.Any(), Arg.Any()); + } } From bcc2960c79b69259a702a68ba8b89c8f3eee2ae3 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:04:37 -0700 Subject: [PATCH 03/40] add comments to tests --- .../SendControlsSyncPolicyValidatorTests.cs | 3 +++ test/Core.Test/Tools/Services/SendValidationServiceTests.cs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs index af2e4f05eb2a..bccb43e6cf58 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs @@ -19,6 +19,9 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidat [SutProviderCustomize] public class SendControlsSyncPolicyValidatorTests { + /// When the pm-31885-send-controls flag is active changes to the SendControls policy should be synced + /// back into the legacy DisableSend and SendOptions policy rows, enabling safe rollback. + /// This test asserts that when the flag is disabled, the sync doesn't occur as it is not necessary. [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_DoesNothing_WhenFlagDisabled( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index 0297943b519a..46da19966a35 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -72,7 +72,7 @@ public async Task StorageRemainingForSendAsync_IndividualPremium_DoesNotCallPric // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert + // Assert - should NOT call pricing service for individual premium users await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } @@ -96,7 +96,7 @@ public async Task StorageRemainingForSendAsync_SelfHosted_DoesNotCallPricingServ // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert + // Assert - should NOT call pricing service for self-hosted await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } @@ -117,7 +117,7 @@ public async Task StorageRemainingForSendAsync_OrgSend_DoesNotCallPricingService // Act var result = await sutProvider.Sut.StorageRemainingForSendAsync(send); - // Assert + // Assert - should NOT call pricing service for org sends await sutProvider.GetDependency().DidNotReceive().GetAvailablePremiumPlan(); } From d52424c679bfac439f531b9c05431bcd93d789fa Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:56:09 -0700 Subject: [PATCH 04/40] Apply suggestion from @mkincaid-bw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • `IX_Policy_OrganizationId_Type` is a unique index Co-authored-by: mkincaid-bw --- ...26-02-28_00_CreateSendControlsPolicies.sql | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql index 38a42451d4d7..52ff719518bc 100644 --- a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql +++ b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql @@ -33,16 +33,38 @@ BEGIN GETUTCDATE(), GETUTCDATE() FROM ( - SELECT - COALESCE(ds.OrganizationId, so.OrganizationId) AS OrganizationId, - ds.Enabled AS DisableSendEnabled, - so.Enabled AS SendOptionsEnabled, - so.Data AS SendOptionsData - FROM [dbo].[Policy] ds - FULL OUTER JOIN [dbo].[Policy] so - ON ds.OrganizationId = so.OrganizationId + SELECT DISTINCT + COALESCE(ds.OrganizationId, so.OrganizationId) AS OrganizationId, + ds.Enabled AS DisableSendEnabled, + so.Enabled AS SendOptionsEnabled, + so.Data AS SendOptionsData + FROM + [dbo].[Policy] ds + LEFT JOIN + [dbo].[Policy] so + ON ds.OrganizationId = so.OrganizationId AND so.Type = @SendOptionsType - WHERE (ds.Type = @DisableSendType OR so.Type = @SendOptionsType) + WHERE + ds.Type = @DisableSendType + UNION + SELECT + so.OrganizationId, + NULL, + so.Enabled, + so.Data + FROM + [dbo].[Policy] so + WHERE + so.Type = @SendOptionsType + AND NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[Policy] ds + WHERE + ds.OrganizationId = so.OrganizationId + AND ds.Type = @DisableSendType + ) combined -- Skip orgs that already have a SendControls row WHERE NOT EXISTS ( From 235d832ddcd712e8e741da9f8410492e3f64c678 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:07:27 -0700 Subject: [PATCH 05/40] renamne migrations for correct sorting --- ...r.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} | 0 ...lsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} | 0 ...r.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} | 0 ...lsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} | 0 ...r.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} | 0 ...lsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename util/MySqlMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.Designer.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} (100%) rename util/MySqlMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} (100%) rename util/PostgresMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.Designer.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} (100%) rename util/PostgresMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} (100%) rename util/SqliteMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.Designer.cs => 20260306000000_CreateSendControlsPolicies.Designer.cs} (100%) rename util/SqliteMigrations/Migrations/{20260228000000_CreateSendControlsPolicies.cs => 20260306000000_CreateSendControlsPolicies.cs} (100%) diff --git a/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs rename to util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs diff --git a/util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs rename to util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs diff --git a/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs similarity index 100% rename from util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs rename to util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs diff --git a/util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs similarity index 100% rename from util/PostgresMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs rename to util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs diff --git a/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.Designer.cs rename to util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs diff --git a/util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20260228000000_CreateSendControlsPolicies.cs rename to util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs From 4a0728c9326ae8efdc8e3d39993bf27b865ea532 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:06:05 -0700 Subject: [PATCH 06/40] respond to csharp related review comments --- .../PolicyServiceCollectionExtensions.cs | 6 +-- ...dator.cs => DisableSendSyncPolicyEvent.cs} | 34 +++++++---------- ...ator.cs => SendControlsSyncPolicyEvent.cs} | 36 +++++++++--------- ...dator.cs => SendOptionsSyncPolicyEvent.cs} | 37 +++++++------------ .../DisableSendSyncPolicyValidatorTests.cs | 6 +-- .../SendControlsSyncPolicyValidatorTests.cs | 10 ++--- .../SendOptionsSyncPolicyValidatorTests.cs | 6 +-- 7 files changed, 59 insertions(+), 76 deletions(-) rename src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{DisableSendSyncPolicyValidator.cs => DisableSendSyncPolicyEvent.cs} (57%) rename src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{SendControlsSyncPolicyValidator.cs => SendControlsSyncPolicyEvent.cs} (64%) rename src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{SendOptionsSyncPolicyValidator.cs => SendOptionsSyncPolicyEvent.cs} (56%) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 12bdd80d2e17..4441d1aca82c 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -63,9 +63,9 @@ private static void AddPolicyUpdateEvents(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void AddPolicyRequirements(this IServiceCollection services) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs similarity index 57% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs index 32d085b9da0f..c65c565928ed 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; @@ -15,7 +13,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls /// always stays current for when the flag is eventually enabled. /// -public class DisableSendSyncPolicyValidator(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +public class DisableSendSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.DisableSend; @@ -27,28 +25,24 @@ public async Task ExecutePostUpsertSideEffectAsync( var policyUpdate = policyRequest.PolicyUpdate; var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendControls); - - var data = sendControlsPolicy != null - ? CoreHelpers.LoadClassFromJsonData(sendControlsPolicy.Data) ?? new SendControlsPolicyData() - : new SendControlsPolicyData(); - - data.DisableSend = postUpsertedPolicyState.Enabled; - - var policy = sendControlsPolicy ?? new Policy + policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy { + Id = CoreHelpers.GenerateComb(), OrganizationId = policyUpdate.OrganizationId, Type = PolicyType.SendControls, }; - if (sendControlsPolicy == null) - { - policy.SetNewId(); - } + var sendControlsPolicyData = + sendControlsPolicy.GetDataModel(); + + sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled; - policy.Enabled = data.DisableSend || data.DisableHideEmail; - policy.SetDataModel(data); + // TODO: seek clarification on review comment: sendControlsPolicyData.DisableHideEmail not mapped during this event + // DisableHideEmail mapping should be handled in + // src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs + sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; + sendControlsPolicy.SetDataModel(sendControlsPolicyData); - await policyRepository.UpsertAsync(policy); + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs similarity index 64% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs index 405bc564a176..f748a789ea30 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; @@ -15,9 +13,9 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// When the pm-31885-send-controls flag is active, syncs changes to the SendControls policy /// back into the legacy DisableSend and SendOptions policy rows, enabling safe rollback. /// -public class SendControlsSyncPolicyValidator( +public class SendControlsSyncPolicyEvent( IPolicyRepository policyRepository, - IFeatureService featureService) : IOnPolicyPostUpdateEvent + TimeProvider timeProvider) : IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.SendControls; @@ -26,25 +24,30 @@ public async Task ExecutePostUpsertSideEffectAsync( Policy postUpsertedPolicyState, Policy? previousPolicyState) { - if (!featureService.IsEnabled(FeatureFlagKeys.SendControls)) + var policyUpdate = policyRequest.PolicyUpdate; + + var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy { - return; - } + Id = CoreHelpers.GenerateComb(), + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; - var data = CoreHelpers.LoadClassFromJsonData(postUpsertedPolicyState.Data) - ?? new SendControlsPolicyData(); + var sendControlsPolicyData = + sendControlsPolicy.GetDataModel(); await UpsertLegacyPolicyAsync( policyRequest.PolicyUpdate.OrganizationId, PolicyType.DisableSend, - enabled: postUpsertedPolicyState.Enabled && data.DisableSend, + enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableSend, policyData: null); - var sendOptionsData = new SendOptionsPolicyData { DisableHideEmail = data.DisableHideEmail }; + var sendOptionsData = new SendOptionsPolicyData { DisableHideEmail = sendControlsPolicyData.DisableHideEmail }; await UpsertLegacyPolicyAsync( policyRequest.PolicyUpdate.OrganizationId, PolicyType.SendOptions, - enabled: postUpsertedPolicyState.Enabled && data.DisableHideEmail, + enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableHideEmail, policyData: CoreHelpers.ClassToJsonData(sendOptionsData)); } @@ -56,11 +59,7 @@ private async Task UpsertLegacyPolicyAsync( { var existing = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, type); - var policy = existing ?? new Policy - { - OrganizationId = organizationId, - Type = type, - }; + var policy = existing ?? new Policy { OrganizationId = organizationId, Type = type, }; if (existing == null) { @@ -69,6 +68,7 @@ private async Task UpsertLegacyPolicyAsync( policy.Enabled = enabled; policy.Data = policyData; + policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; await policyRepository.UpsertAsync(policy); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs similarity index 56% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index 1df362d30688..9a2d12128666 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; @@ -15,7 +13,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls /// always stays current for when the flag is eventually enabled. /// -public class SendOptionsSyncPolicyValidator(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +public class SendOptionsSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.SendOptions; @@ -26,33 +24,24 @@ public async Task ExecutePostUpsertSideEffectAsync( { var policyUpdate = policyRequest.PolicyUpdate; - var parsedSendOptions = postUpsertedPolicyState.Enabled - ? CoreHelpers.LoadClassFromJsonData(postUpsertedPolicyState.Data) - : null; - var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendControls); - - var data = sendControlsPolicy != null - ? CoreHelpers.LoadClassFromJsonData(sendControlsPolicy.Data) ?? new SendControlsPolicyData() - : new SendControlsPolicyData(); - - data.DisableHideEmail = parsedSendOptions?.DisableHideEmail ?? false; - - var policy = sendControlsPolicy ?? new Policy + policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy { + Id = CoreHelpers.GenerateComb(), OrganizationId = policyUpdate.OrganizationId, Type = PolicyType.SendControls, }; - if (sendControlsPolicy == null) - { - policy.SetNewId(); - } + var sendControlsPolicyData = + sendControlsPolicy.GetDataModel(); + + // Right now, SendOptions is only used to contain DisableHideEmail + // Future SendOptions will be added and mapped here + sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.Enabled; - policy.Enabled = data.DisableSend || data.DisableHideEmail; - policy.SetDataModel(data); + sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; + sendControlsPolicy.SetDataModel(sendControlsPolicyData); - await policyRepository.UpsertAsync(policy); + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs index e93ad492c4f8..835c58f0c2ab 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs @@ -22,7 +22,7 @@ public class DisableSendSyncPolicyValidatorTests public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( [PolicyUpdate(PolicyType.DisableSend, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.DisableSend, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; sutProvider.GetDependency() @@ -46,7 +46,7 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPo [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -72,7 +72,7 @@ public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenEithe [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs index bccb43e6cf58..6577bcf73345 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs @@ -26,7 +26,7 @@ public class SendControlsSyncPolicyValidatorTests public async Task ExecutePostUpsertSideEffectAsync_DoesNothing_WhenFlagDisabled( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; sutProvider.GetDependency() @@ -45,7 +45,7 @@ await sutProvider.GetDependency() public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisableSendPolicy( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); @@ -72,7 +72,7 @@ await sutProvider.GetDependency() public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableHideEmail_ToLegacySendOptionsPolicy( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); @@ -100,7 +100,7 @@ await sutProvider.GetDependency() public async Task ExecutePostUpsertSideEffectAsync_DisablesLegacyPolicies_WhenSendControlsPolicyDisabled( [PolicyUpdate(PolicyType.SendControls, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: false)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); @@ -133,7 +133,7 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingLegacyPolicies [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs index 0f03b02fdac5..48b9c1babe80 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs @@ -22,7 +22,7 @@ public class SendOptionsSyncPolicyValidatorTests public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); @@ -48,7 +48,7 @@ public async Task ExecutePostUpsertSideEffectAsync_ClearsDisableHideEmail_WhenPo [PolicyUpdate(PolicyType.SendOptions, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendOptions, enabled: false)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); @@ -74,7 +74,7 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPo [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: false)] Policy existingSendControlsPolicy, - SutProvider sutProvider) + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); From 16cffeb64bf569167e9d8d882677d9ee3025bca6 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:25:28 -0700 Subject: [PATCH 07/40] fix failing lints --- .../PolicyValidators/DisableSendSyncPolicyEvent.cs | 10 +++++----- .../PolicyValidators/SendControlsSyncPolicyEvent.cs | 11 +++++------ .../PolicyValidators/SendOptionsSyncPolicyEvent.cs | 10 +++++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs index c65c565928ed..fc9e28b740dc 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs @@ -26,11 +26,11 @@ public async Task ExecutePostUpsertSideEffectAsync( var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy - { - Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, - Type = PolicyType.SendControls, - }; + { + Id = CoreHelpers.GenerateComb(), + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs index f748a789ea30..ab186a42ad06 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs @@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Services; using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; @@ -28,11 +27,11 @@ public async Task ExecutePostUpsertSideEffectAsync( var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy - { - Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, - Type = PolicyType.SendControls, - }; + { + Id = CoreHelpers.GenerateComb(), + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index 9a2d12128666..72abc956dcde 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -26,11 +26,11 @@ public async Task ExecutePostUpsertSideEffectAsync( var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy - { - Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, - Type = PolicyType.SendControls, - }; + { + Id = CoreHelpers.GenerateComb(), + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendControls, + }; var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); From ea8527e252a550d70ba1b279d29656f322573b01 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:36:04 -0700 Subject: [PATCH 08/40] fix tests --- .../SendControlsSyncPolicyValidatorTests.cs | 53 ++++++------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs index 6577bcf73345..8d7d8cc086e8 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs @@ -6,7 +6,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Services; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -19,28 +18,6 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidat [SutProviderCustomize] public class SendControlsSyncPolicyValidatorTests { - /// When the pm-31885-send-controls flag is active changes to the SendControls policy should be synced - /// back into the legacy DisableSend and SendOptions policy rows, enabling safe rollback. - /// This test asserts that when the flag is disabled, the sync doesn't occur as it is not necessary. - [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_DoesNothing_WhenFlagDisabled( - [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) - { - postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.SendControls) - .Returns(false); - - await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( - new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - - await sutProvider.GetDependency() - .DidNotReceive() - .UpsertAsync(Arg.Any()); - } - [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisableSendPolicy( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, @@ -50,11 +27,11 @@ public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisa postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.SendControls) - .Returns(true); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(postUpsertedPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -77,11 +54,11 @@ public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableHideEmail_ToLegac postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.SendControls) - .Returns(true); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(postUpsertedPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -105,11 +82,11 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesLegacyPolicies_WhenSe postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.SendControls) - .Returns(true); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Any()) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(postUpsertedPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -140,9 +117,9 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingLegacyPolicies existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.SendControls) - .Returns(true); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns(postUpsertedPolicy); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) .Returns(existingDisableSendPolicy); From 783b426943d1fd768483b06086ae3701329c4aad Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:39:15 -0700 Subject: [PATCH 09/40] revise policy sync logic --- .../DisableSendSyncPolicyEvent.cs | 15 ++++-- .../SendOptionsSyncPolicyEvent.cs | 7 +-- .../CreateSendControlsPolicyMigrationTest.cs | 3 ++ ....cs => DisableSendSyncPolicyEventTests.cs} | 50 +++++++++++++++++-- ....cs => SendOptionsSyncPolicyEventTests.cs} | 42 +++++++++++++++- 5 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs rename test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{DisableSendSyncPolicyValidatorTests.cs => DisableSendSyncPolicyEventTests.cs} (60%) rename test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{SendOptionsSyncPolicyValidatorTests.cs => SendOptionsSyncPolicyEventTests.cs} (67%) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs index fc9e28b740dc..ea53ac0515b8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs @@ -32,14 +32,21 @@ public async Task ExecutePostUpsertSideEffectAsync( Type = PolicyType.SendControls, }; + var sendOptionsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + policyUpdate.OrganizationId, PolicyType.SendOptions) ?? new Policy + { + Id = CoreHelpers.GenerateComb(), + OrganizationId = policyUpdate.OrganizationId, + Type = PolicyType.SendOptions, + }; + var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); - sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled; + var sendOptionsPolicyData = sendOptionsPolicy.GetDataModel(); - // TODO: seek clarification on review comment: sendControlsPolicyData.DisableHideEmail not mapped during this event - // DisableHideEmail mapping should be handled in - // src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs + sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled; + sendControlsPolicyData.DisableHideEmail = sendOptionsPolicy.Enabled && sendOptionsPolicyData.DisableHideEmail; sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; sendControlsPolicy.SetDataModel(sendControlsPolicyData); diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index 72abc956dcde..f8504e556265 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -35,10 +35,11 @@ public async Task ExecutePostUpsertSideEffectAsync( var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); - // Right now, SendOptions is only used to contain DisableHideEmail - // Future SendOptions will be added and mapped here - sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.Enabled; + var disableSendPolicyState = await policyRepository.GetByOrganizationIdTypeAsync( + policyUpdate.OrganizationId, PolicyType.DisableSend); + sendControlsPolicyData.DisableSend = disableSendPolicyState?.Enabled ?? false; + sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.Enabled && postUpsertedPolicyState.GetDataModel().DisableHideEmail; sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; sendControlsPolicy.SetDataModel(sendControlsPolicyData); diff --git a/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs b/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs new file mode 100644 index 000000000000..652071d97415 --- /dev/null +++ b/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs @@ -0,0 +1,3 @@ +// TODO remove after testing +// This file will be included to demonstrate testing performed to code reviewers, but should not be merged + diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs similarity index 60% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs index 835c58f0c2ab..4b2af63eef0b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs @@ -16,7 +16,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; [SutProviderCustomize] -public class DisableSendSyncPolicyValidatorTests +public class DisableSendSyncPolicyEventTests { [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( @@ -28,6 +28,9 @@ public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_ sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns((Policy?)null); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -55,6 +58,9 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPo sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -72,16 +78,48 @@ public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenEithe [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, + [Policy(PolicyType.SendOptions, enabled: true)] Policy existingSendOptionsPolicy, SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; - // DisableSend is being turned off, but DisableHideEmail is still true - existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); + // DisableSend is being turned off, but SendOptions is still enabled + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Enabled == true)); // stays enabled because SendOptions is still enabled + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenSendOptionsEnabled_AndSendControlsDidNotExist( + [PolicyUpdate(PolicyType.DisableSend, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.DisableSend, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.SendOptions, enabled: true)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns((Policy?)null); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -89,6 +127,10 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) .UpsertAsync(Arg.Is(p => - p.Enabled == true)); // stays enabled because DisableHideEmail is still true + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.SendControls && + p.Enabled == true && + CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true && + CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs similarity index 67% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs index 48b9c1babe80..f63cfdd68d01 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs @@ -16,7 +16,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; [SutProviderCustomize] -public class SendOptionsSyncPolicyValidatorTests +public class SendOptionsSyncPolicyEventTests { [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( @@ -30,6 +30,9 @@ public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_ sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns((Policy?)null); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -58,6 +61,9 @@ public async Task ExecutePostUpsertSideEffectAsync_ClearsDisableHideEmail_WhenPo sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -84,6 +90,9 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPo sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -95,4 +104,35 @@ await sutProvider.GetDependency() p.Enabled == true && (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenDisableSendEnabled_AndSendControlsDidNotExist( + [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: true)] Policy existingDisableSendPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) + .Returns((Policy?)null); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.OrganizationId == policyUpdate.OrganizationId && + p.Type == PolicyType.SendControls && + p.Enabled == true && + CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true && + CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); + } } From f8bbfa03c4e364071859d55f14b659dd64ea3b59 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:52:43 -0700 Subject: [PATCH 10/40] revise policy event logic and tests --- .../DisableSendSyncPolicyEvent.cs | 31 +++++++------ .../SendOptionsSyncPolicyEvent.cs | 24 +++++----- .../DisableSendSyncPolicyEventTests.cs | 5 ++- ...cs => SendControlsSyncPolicyEventTests.cs} | 2 +- .../SendOptionsSyncPolicyEventTests.cs | 44 +------------------ 5 files changed, 33 insertions(+), 73 deletions(-) rename test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/{SendControlsSyncPolicyValidatorTests.cs => SendControlsSyncPolicyEventTests.cs} (99%) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs index ea53ac0515b8..2f6b1ada8052 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs @@ -22,34 +22,33 @@ public async Task ExecutePostUpsertSideEffectAsync( Policy postUpsertedPolicyState, Policy? previousPolicyState) { - var policyUpdate = policyRequest.PolicyUpdate; + var organizationId = policyRequest.PolicyUpdate.OrganizationId; + // Step 1: sync DisableSend.Enabled -> SendControlsPolicy.Data.DisableSend var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy + organizationId, PolicyType.SendControls) ?? new Policy { Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, + OrganizationId = organizationId, Type = PolicyType.SendControls, }; var sendOptionsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendOptions) ?? new Policy - { - Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, - Type = PolicyType.SendOptions, - }; - - var sendControlsPolicyData = - sendControlsPolicy.GetDataModel(); - - var sendOptionsPolicyData = sendOptionsPolicy.GetDataModel(); + organizationId, PolicyType.SendOptions); + var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled; - sendControlsPolicyData.DisableHideEmail = sendOptionsPolicy.Enabled && sendOptionsPolicyData.DisableHideEmail; - sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; + if (sendOptionsPolicy?.Enabled == true) + { + sendControlsPolicyData.DisableHideEmail = + sendOptionsPolicy.GetDataModel().DisableHideEmail; + } + sendControlsPolicy.SetDataModel(sendControlsPolicyData); + // Step 2: sync Enabled status. SendControlsPolicy is enabled if either legacy policy is enabled + sendControlsPolicy.Enabled = postUpsertedPolicyState.Enabled || (sendOptionsPolicy?.Enabled ?? false); + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index f8504e556265..028f8c088b43 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -22,27 +22,27 @@ public async Task ExecutePostUpsertSideEffectAsync( Policy postUpsertedPolicyState, Policy? previousPolicyState) { - var policyUpdate = policyRequest.PolicyUpdate; + var organizationId = policyRequest.PolicyUpdate.OrganizationId; + // Step 1: sync SendOptionsPolicy.Data.DisableSend -> SendControlsPolicy.Data.DisableSend var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy + organizationId, PolicyType.SendControls) ?? new Policy { Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, + OrganizationId = organizationId, Type = PolicyType.SendControls, }; - var sendControlsPolicyData = - sendControlsPolicy.GetDataModel(); - - var disableSendPolicyState = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.DisableSend); - - sendControlsPolicyData.DisableSend = disableSendPolicyState?.Enabled ?? false; - sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.Enabled && postUpsertedPolicyState.GetDataModel().DisableHideEmail; - sendControlsPolicy.Enabled = sendControlsPolicyData.DisableSend || sendControlsPolicyData.DisableHideEmail; + var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); + sendControlsPolicyData.DisableHideEmail = postUpsertedPolicyState.GetDataModel().DisableHideEmail; sendControlsPolicy.SetDataModel(sendControlsPolicyData); + // Step 2: sync Enabled status. SendControlsPolicy is enabled if either legacy policy is enabled + // Optimization: DisableSendPolicy.Enabled maps to SendControlsPolicy.Data.DisableSend - so we can use that + // as a proxy for that legacy policy state + sendControlsPolicy.Enabled = postUpsertedPolicyState.Enabled || + sendControlsPolicyData.DisableSend; + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs index 4b2af63eef0b..e2bb203974bc 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs @@ -74,7 +74,7 @@ await sutProvider.GetDependency() } [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenEitherFieldIsTrue( + public async Task ExecutePostUpsertSideEffectAsync_SendControlsRemainsEnabled_WhenSendOptionsStillEnabled( [PolicyUpdate(PolicyType.DisableSend, enabled: false)] PolicyUpdate policyUpdate, [Policy(PolicyType.DisableSend, enabled: false)] Policy postUpsertedPolicy, [Policy(PolicyType.SendControls, enabled: true)] Policy existingSendControlsPolicy, @@ -100,7 +100,8 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) .UpsertAsync(Arg.Is(p => - p.Enabled == true)); // stays enabled because SendOptions is still enabled + p.Enabled == true && // stays enabled because SendOptions is still enabled + CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); } [Theory, BitAutoData] diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs index 8d7d8cc086e8..f483c967c59e 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs @@ -16,7 +16,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; [SutProviderCustomize] -public class SendControlsSyncPolicyValidatorTests +public class SendControlsSyncPolicyEventTests { [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisableSendPolicy( diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs index f63cfdd68d01..e32a3c023436 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs @@ -18,34 +18,6 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidat [SutProviderCustomize] public class SendOptionsSyncPolicyEventTests { - [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( - [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, - SutProvider sutProvider) - { - postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); - - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) - .Returns((Policy?)null); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) - .Returns((Policy?)null); - - await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( - new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - - await sutProvider.GetDependency() - .Received(1) - .UpsertAsync(Arg.Is(p => - p.OrganizationId == policyUpdate.OrganizationId && - p.Type == PolicyType.SendControls && - p.Enabled == true && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); - } - [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_ClearsDisableHideEmail_WhenPolicyDisabled( [PolicyUpdate(PolicyType.SendOptions, enabled: false)] PolicyUpdate policyUpdate, @@ -54,16 +26,13 @@ public async Task ExecutePostUpsertSideEffectAsync_ClearsDisableHideEmail_WhenPo SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = false }); existingSendControlsPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendControlsPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) - .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -90,9 +59,6 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingSendControlsPo sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns(existingSendControlsPolicy); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) - .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -106,22 +72,17 @@ await sutProvider.GetDependency() } [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_SendControlsEnabled_WhenDisableSendEnabled_AndSendControlsDidNotExist( + public async Task ExecutePostUpsertSideEffectAsync_CreatesNewSendControlsPolicy_WhenNoneExists( [PolicyUpdate(PolicyType.SendOptions, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendOptions, enabled: true)] Policy postUpsertedPolicy, - [Policy(PolicyType.DisableSend, enabled: true)] Policy existingDisableSendPolicy, SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); - existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) .Returns((Policy?)null); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) - .Returns(existingDisableSendPolicy); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); @@ -132,7 +93,6 @@ await sutProvider.GetDependency() p.OrganizationId == policyUpdate.OrganizationId && p.Type == PolicyType.SendControls && p.Enabled == true && - CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true && CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); } } From a6ea8539596cde221a6bc74b825981896b3f5e59 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:13:09 -0700 Subject: [PATCH 11/40] add integration tests - fix SQL syntax error - escape Sqlite format specifier - update migration IDs to match sorted filename - fix SQL syntax error --- .../CreateSendControlsPolicyMigrationTest.cs | 3 - ...reateSendControlsPoliciesMigrationTests.cs | 321 ++++++++++++++++++ ...26-02-28_00_CreateSendControlsPolicies.sql | 2 +- ...000_CreateSendControlsPolicies.Designer.cs | 2 +- ...000_CreateSendControlsPolicies.Designer.cs | 2 +- ...000_CreateSendControlsPolicies.Designer.cs | 2 +- ...260306000000_CreateSendControlsPolicies.cs | 6 +- 7 files changed, 328 insertions(+), 10 deletions(-) delete mode 100644 test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs create mode 100644 test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs diff --git a/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs b/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs deleted file mode 100644 index 652071d97415..000000000000 --- a/test/Api.Test/Tools/SendControlsPolicyIntegrationTests/CreateSendControlsPolicyMigrationTest.cs +++ /dev/null @@ -1,3 +0,0 @@ -// TODO remove after testing -// This file will be included to demonstrate testing performed to code reviewers, but should not be merged - diff --git a/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs b/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs new file mode 100644 index 000000000000..3bf83fcd602f --- /dev/null +++ b/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs @@ -0,0 +1,321 @@ +// PM-31885 — delete this test file after the CreateSendControlsPolicies transition migration +// has been run in all environments. + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Repositories; +using Bit.Core.Utilities; +using Bit.Infrastructure.IntegrationTest.AdminConsole; +using Bit.Infrastructure.IntegrationTest.Services; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.Tools.Migrations; + +public class CreateSendControlsPoliciesMigrationTests +{ + /// + /// Org with only DisableSend enabled — migration creates a SendControls row with disableSend=true. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesEnabledSendControls_WhenOnlyDisableSendEnabled( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: false); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with only DisableSend disabled — migration still creates a SendControls row, but disabled. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesDisabledSendControls_WhenOnlyDisableSendDisabled( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: false); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + AssertSendControlsPolicy(result, org.Id, expectedEnabled: false, expectedDisableSend: false, expectedDisableHideEmail: false); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with only SendOptions enabled, disableHideEmail=true in data — migration creates an enabled + /// SendControls row with disableHideEmail=true. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesEnabledSendControls_WhenOnlySendOptionsEnabledWithDisableHideEmail( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, + data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: false, expectedDisableHideEmail: true); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with SendOptions enabled but disableHideEmail=false in data — SendControls is still enabled + /// (because SendOptions was enabled), but neither data field is enforced. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesEnabledSendControls_WhenSendOptionsEnabledWithoutDisableHideEmail( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, + data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = false })); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: false, expectedDisableHideEmail: false); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with both source policies enabled and disableHideEmail=true — full enforcement migrated. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesSendControls_WithBothFieldsEnabled_WhenBothSourcePoliciesEnabled( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, + data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: true); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with both source policies disabled but disableHideEmail=true in SendOptions data — + /// SendControls is disabled, but the data field value is preserved from the JSON (no Enabled guard + /// on the disableHideEmail field in the migration SQL). + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesDisabledSendControls_WhenBothSourcePoliciesDisabled( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: false); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: false, + data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + // disableHideEmail is copied from the data JSON regardless of SendOptions.Enabled + AssertSendControlsPolicy(result, org.Id, expectedEnabled: false, expectedDisableSend: false, expectedDisableHideEmail: true); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with DisableSend enabled but SendOptions disabled — SendControls is enabled via DisableSend. + /// disableHideEmail is still copied from the SendOptions data JSON regardless of its Enabled state. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_CreatesSendControls_WithDisableHideEmailFromData_WhenSendOptionsDisabled( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: false, + data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + // disableHideEmail is copied from the data JSON regardless of SendOptions.Enabled + AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: true); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org with neither DisableSend nor SendOptions — no SendControls row is created. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_DoesNotCreateSendControls_WhenNoSourcePoliciesExist( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + Assert.Null(result); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Org that already has a SendControls row — the WHERE NOT EXISTS guard means the existing row is + /// left completely unchanged and no duplicate is inserted. + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_SkipsOrg_WhenSendControlsAlreadyExists( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var org = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); + var preExistingSendControls = await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendControls, + enabled: false, + data: CoreHelpers.ClassToJsonData(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false })); + + // Act + migrationTester.ApplyMigration(); + + // Assert — the pre-existing row is returned unchanged; no duplicate was created + var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); + Assert.NotNull(result); + Assert.Equal(preExistingSendControls.Id, result.Id); + Assert.False(result.Enabled); + var data = CoreHelpers.LoadClassFromJsonData(result.Data); + Assert.NotNull(data); + Assert.False(data.DisableSend); + Assert.False(data.DisableHideEmail); + + // Annul + await organizationRepository.DeleteAsync(org); + } + + /// + /// Two orgs processed together — each org's result is independent (no cross-org contamination + /// in the JOIN / COALESCE logic). + /// + [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] + public async Task Migration_IsIsolatedPerOrganization( + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IMigrationTesterService migrationTester) + { + // Arrange + var orgWithPolicy = await organizationRepository.CreateTestOrganizationAsync(); + await CreatePolicyAsync(policyRepository, orgWithPolicy.Id, PolicyType.DisableSend, enabled: true); + + var orgWithoutPolicy = await organizationRepository.CreateTestOrganizationAsync(); + + // Act + migrationTester.ApplyMigration(); + + // Assert + var resultWithPolicy = await policyRepository.GetByOrganizationIdTypeAsync(orgWithPolicy.Id, PolicyType.SendControls); + AssertSendControlsPolicy(resultWithPolicy, orgWithPolicy.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: false); + + var resultWithoutPolicy = await policyRepository.GetByOrganizationIdTypeAsync(orgWithoutPolicy.Id, PolicyType.SendControls); + Assert.Null(resultWithoutPolicy); + + // Annul + await organizationRepository.DeleteAsync(orgWithPolicy); + await organizationRepository.DeleteAsync(orgWithoutPolicy); + } + + private static Task CreatePolicyAsync( + IPolicyRepository policyRepository, + Guid organizationId, + PolicyType type, + bool enabled, + string? data = null) + => policyRepository.CreateAsync(new Policy + { + OrganizationId = organizationId, + Type = type, + Enabled = enabled, + Data = data, + }); + + private static void AssertSendControlsPolicy( + Policy? policy, + Guid organizationId, + bool expectedEnabled, + bool expectedDisableSend, + bool expectedDisableHideEmail) + { + Assert.NotNull(policy); + Assert.Equal(organizationId, policy.OrganizationId); + Assert.Equal(PolicyType.SendControls, policy.Type); + Assert.Equal(expectedEnabled, policy.Enabled); + Assert.NotEqual(default, policy.CreationDate); + Assert.NotEqual(default, policy.RevisionDate); + var data = CoreHelpers.LoadClassFromJsonData(policy.Data); + Assert.NotNull(data); + Assert.Equal(expectedDisableSend, data.DisableSend); + Assert.Equal(expectedDisableHideEmail, data.DisableHideEmail); + } +} diff --git a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql index 52ff719518bc..52603b040cbc 100644 --- a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql +++ b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql @@ -64,7 +64,7 @@ BEGIN WHERE ds.OrganizationId = so.OrganizationId AND ds.Type = @DisableSendType - + ) ) combined -- Skip orgs that already have a SendControls row WHERE NOT EXISTS ( diff --git a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs index 3c9419eecbc4..db3d4ed1667c 100644 --- a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.MySqlMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20260228000000_CreateSendControlsPolicies")] + [Migration("20260306000000_CreateSendControlsPolicies")] partial class CreateSendControlsPolicies { /// diff --git a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs index ca57dc806bc8..aeb6b3041434 100644 --- a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.PostgresMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20260228000000_CreateSendControlsPolicies")] + [Migration("20260306000000_CreateSendControlsPolicies")] partial class CreateSendControlsPolicies { /// diff --git a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs index a716a8c55c8f..e0e7b4601c0b 100644 --- a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs @@ -11,7 +11,7 @@ namespace Bit.SqliteMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20260228000000_CreateSendControlsPolicies")] + [Migration("20260306000000_CreateSendControlsPolicies")] partial class CreateSendControlsPolicies { /// diff --git a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs index b319bbcae75c..379f43120fba 100644 --- a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs +++ b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs @@ -23,14 +23,14 @@ SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || COALESCE(ds.""OrganizationId"", so.""OrganizationId""), 20, CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 OR IFNULL(so.""Enabled"", 0) = 1 THEN 1 ELSE 0 END, - '{""disableSend"":' || + '{{""disableSend"":' || CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 THEN 'true' ELSE 'false' END || ',""disableHideEmail"":' || CASE WHEN so.""Data"" IS NOT NULL AND json_valid(so.""Data"") = 1 AND json_extract(so.""Data"", '$.disableHideEmail') = 1 THEN 'true' ELSE 'false' END || - '}', + '}}', datetime('now'), datetime('now') FROM (SELECT ""OrganizationId"", ""Enabled"", ""Data"" FROM ""Policy"" WHERE ""Type"" = 7) so @@ -53,7 +53,7 @@ SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || ds.""OrganizationId"", 20, ds.""Enabled"", - '{""disableSend"":' || CASE WHEN ds.""Enabled"" = 1 THEN 'true' ELSE 'false' END || ',""disableHideEmail"":false}', + '{{""disableSend"":' || CASE WHEN ds.""Enabled"" = 1 THEN 'true' ELSE 'false' END || ',""disableHideEmail"":false}}', datetime('now'), datetime('now') FROM (SELECT ""OrganizationId"", ""Enabled"" FROM ""Policy"" WHERE ""Type"" = 6) ds From ac6710d59593944d6c6e8fcf428f55ae324e89d8 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:06:10 -0700 Subject: [PATCH 12/40] OR legacy policy data with SendControls policy data --- .../Services/SendValidationService.cs | 21 ++++++-- .../Services/SendValidationServiceTests.cs | 48 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index bf2f91ef16c8..0f21ce9f5c6a 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -50,16 +50,29 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) return; } + #region Fetch Policy Requirements Async + var sendControlsTask = _policyRequirementQuery.GetAsync(userId.Value); + var disableSendTask = _policyRequirementQuery.GetAsync(userId.Value); + var sendOptionsTask = _policyRequirementQuery.GetAsync(userId.Value); + + await Task.WhenAll(sendControlsTask, disableSendTask, sendOptionsTask); + + var sendControlsRequirement = sendControlsTask.Result; + var disableSendRequirement = disableSendTask.Result; + var sendOptionsRequirement = sendOptionsTask.Result; + #endregion + if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) { - var sendControlsRequirement = await _policyRequirementQuery.GetAsync(userId.Value); - if (sendControlsRequirement.DisableSend) + + if (sendControlsRequirement.DisableSend || disableSendRequirement.DisableSend) { throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send."); } - if (sendControlsRequirement.DisableHideEmail && send.HideEmail.GetValueOrDefault()) + if ((sendControlsRequirement.DisableHideEmail || sendOptionsRequirement.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."); } @@ -67,13 +80,11 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) return; } - var disableSendRequirement = await _policyRequirementQuery.GetAsync(userId.Value); if (disableSendRequirement.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()) { 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/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index 46da19966a35..fc6fd487a93c 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -188,6 +188,28 @@ public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhe sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = true, DisableHideEmail = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + Assert.Contains("you are only able to delete an existing Send", exception.Message); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenLegacyDisableSendApplies( + SutProvider sutProvider, Send send, Guid userId) + { + send.HideEmail = false; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = true }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); @@ -202,6 +224,28 @@ public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhe sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = true }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + Assert.Contains("you are not allowed to hide your email address", exception.Message); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenLegacySendOptionsDisableHideEmailApplies( + SutProvider sutProvider, Send send, Guid userId) + { + send.HideEmail = true; + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = true }); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); @@ -216,6 +260,10 @@ public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_NoPolicyR sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); // No exception implies success await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); From 8bc9f7d4723623b910d476767e81960ed71df77f Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:13:46 -0700 Subject: [PATCH 13/40] remove migrations and associated integration test --- ...reateSendControlsPoliciesMigrationTests.cs | 321 -- ...26-02-28_00_CreateSendControlsPolicies.sql | 77 - ...000_CreateSendControlsPolicies.Designer.cs | 3568 ---------------- ...260306000000_CreateSendControlsPolicies.cs | 64 - ...000_CreateSendControlsPolicies.Designer.cs | 3574 ----------------- ...260306000000_CreateSendControlsPolicies.cs | 52 - ...000_CreateSendControlsPolicies.Designer.cs | 3557 ---------------- ...260306000000_CreateSendControlsPolicies.cs | 76 - 8 files changed, 11289 deletions(-) delete mode 100644 test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs delete mode 100644 util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql delete mode 100644 util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs delete mode 100644 util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs delete mode 100644 util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs delete mode 100644 util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs delete mode 100644 util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs delete mode 100644 util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs diff --git a/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs b/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs deleted file mode 100644 index 3bf83fcd602f..000000000000 --- a/test/Infrastructure.IntegrationTest/Tools/Migrations/CreateSendControlsPoliciesMigrationTests.cs +++ /dev/null @@ -1,321 +0,0 @@ -// PM-31885 — delete this test file after the CreateSendControlsPolicies transition migration -// has been run in all environments. - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Repositories; -using Bit.Core.Utilities; -using Bit.Infrastructure.IntegrationTest.AdminConsole; -using Bit.Infrastructure.IntegrationTest.Services; -using Xunit; - -namespace Bit.Infrastructure.IntegrationTest.Tools.Migrations; - -public class CreateSendControlsPoliciesMigrationTests -{ - /// - /// Org with only DisableSend enabled — migration creates a SendControls row with disableSend=true. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesEnabledSendControls_WhenOnlyDisableSendEnabled( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: false); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with only DisableSend disabled — migration still creates a SendControls row, but disabled. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesDisabledSendControls_WhenOnlyDisableSendDisabled( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: false); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - AssertSendControlsPolicy(result, org.Id, expectedEnabled: false, expectedDisableSend: false, expectedDisableHideEmail: false); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with only SendOptions enabled, disableHideEmail=true in data — migration creates an enabled - /// SendControls row with disableHideEmail=true. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesEnabledSendControls_WhenOnlySendOptionsEnabledWithDisableHideEmail( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, - data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: false, expectedDisableHideEmail: true); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with SendOptions enabled but disableHideEmail=false in data — SendControls is still enabled - /// (because SendOptions was enabled), but neither data field is enforced. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesEnabledSendControls_WhenSendOptionsEnabledWithoutDisableHideEmail( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, - data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = false })); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: false, expectedDisableHideEmail: false); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with both source policies enabled and disableHideEmail=true — full enforcement migrated. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesSendControls_WithBothFieldsEnabled_WhenBothSourcePoliciesEnabled( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: true, - data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: true); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with both source policies disabled but disableHideEmail=true in SendOptions data — - /// SendControls is disabled, but the data field value is preserved from the JSON (no Enabled guard - /// on the disableHideEmail field in the migration SQL). - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesDisabledSendControls_WhenBothSourcePoliciesDisabled( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: false); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: false, - data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - // disableHideEmail is copied from the data JSON regardless of SendOptions.Enabled - AssertSendControlsPolicy(result, org.Id, expectedEnabled: false, expectedDisableSend: false, expectedDisableHideEmail: true); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with DisableSend enabled but SendOptions disabled — SendControls is enabled via DisableSend. - /// disableHideEmail is still copied from the SendOptions data JSON regardless of its Enabled state. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_CreatesSendControls_WithDisableHideEmailFromData_WhenSendOptionsDisabled( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendOptions, enabled: false, - data: CoreHelpers.ClassToJsonData(new SendOptionsPolicyData { DisableHideEmail = true })); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - // disableHideEmail is copied from the data JSON regardless of SendOptions.Enabled - AssertSendControlsPolicy(result, org.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: true); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org with neither DisableSend nor SendOptions — no SendControls row is created. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_DoesNotCreateSendControls_WhenNoSourcePoliciesExist( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - Assert.Null(result); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Org that already has a SendControls row — the WHERE NOT EXISTS guard means the existing row is - /// left completely unchanged and no duplicate is inserted. - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_SkipsOrg_WhenSendControlsAlreadyExists( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var org = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, org.Id, PolicyType.DisableSend, enabled: true); - var preExistingSendControls = await CreatePolicyAsync(policyRepository, org.Id, PolicyType.SendControls, - enabled: false, - data: CoreHelpers.ClassToJsonData(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = false })); - - // Act - migrationTester.ApplyMigration(); - - // Assert — the pre-existing row is returned unchanged; no duplicate was created - var result = await policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SendControls); - Assert.NotNull(result); - Assert.Equal(preExistingSendControls.Id, result.Id); - Assert.False(result.Enabled); - var data = CoreHelpers.LoadClassFromJsonData(result.Data); - Assert.NotNull(data); - Assert.False(data.DisableSend); - Assert.False(data.DisableHideEmail); - - // Annul - await organizationRepository.DeleteAsync(org); - } - - /// - /// Two orgs processed together — each org's result is independent (no cross-org contamination - /// in the JOIN / COALESCE logic). - /// - [Theory, DatabaseData(MigrationName = "CreateSendControlsPolicies")] - public async Task Migration_IsIsolatedPerOrganization( - IOrganizationRepository organizationRepository, - IPolicyRepository policyRepository, - IMigrationTesterService migrationTester) - { - // Arrange - var orgWithPolicy = await organizationRepository.CreateTestOrganizationAsync(); - await CreatePolicyAsync(policyRepository, orgWithPolicy.Id, PolicyType.DisableSend, enabled: true); - - var orgWithoutPolicy = await organizationRepository.CreateTestOrganizationAsync(); - - // Act - migrationTester.ApplyMigration(); - - // Assert - var resultWithPolicy = await policyRepository.GetByOrganizationIdTypeAsync(orgWithPolicy.Id, PolicyType.SendControls); - AssertSendControlsPolicy(resultWithPolicy, orgWithPolicy.Id, expectedEnabled: true, expectedDisableSend: true, expectedDisableHideEmail: false); - - var resultWithoutPolicy = await policyRepository.GetByOrganizationIdTypeAsync(orgWithoutPolicy.Id, PolicyType.SendControls); - Assert.Null(resultWithoutPolicy); - - // Annul - await organizationRepository.DeleteAsync(orgWithPolicy); - await organizationRepository.DeleteAsync(orgWithoutPolicy); - } - - private static Task CreatePolicyAsync( - IPolicyRepository policyRepository, - Guid organizationId, - PolicyType type, - bool enabled, - string? data = null) - => policyRepository.CreateAsync(new Policy - { - OrganizationId = organizationId, - Type = type, - Enabled = enabled, - Data = data, - }); - - private static void AssertSendControlsPolicy( - Policy? policy, - Guid organizationId, - bool expectedEnabled, - bool expectedDisableSend, - bool expectedDisableHideEmail) - { - Assert.NotNull(policy); - Assert.Equal(organizationId, policy.OrganizationId); - Assert.Equal(PolicyType.SendControls, policy.Type); - Assert.Equal(expectedEnabled, policy.Enabled); - Assert.NotEqual(default, policy.CreationDate); - Assert.NotEqual(default, policy.RevisionDate); - var data = CoreHelpers.LoadClassFromJsonData(policy.Data); - Assert.NotNull(data); - Assert.Equal(expectedDisableSend, data.DisableSend); - Assert.Equal(expectedDisableHideEmail, data.DisableHideEmail); - } -} diff --git a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql b/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql deleted file mode 100644 index 52603b040cbc..000000000000 --- a/util/Migrator/DbScripts_transition/2026-02-28_00_CreateSendControlsPolicies.sql +++ /dev/null @@ -1,77 +0,0 @@ --- Migrate existing DisableSend (6) and SendOptions (7) policies into new SendControls (20) --- EDD-compatible: only inserts new rows, never modifies existing data - -DECLARE @SendControlsType TINYINT = 20; -DECLARE @DisableSendType TINYINT = 6; -DECLARE @SendOptionsType TINYINT = 7; -DECLARE @BatchSize INT = 2000; -DECLARE @RowsAffected INT = 1; - -WHILE @RowsAffected > 0 -BEGIN - INSERT INTO [dbo].[Policy] ( - [Id], [OrganizationId], [Type], [Enabled], [Data], [CreationDate], [RevisionDate] - ) - SELECT TOP (@BatchSize) - NEWID(), - combined.OrganizationId, - @SendControlsType, - -- Policy is enabled if either old policy was enabled - CASE WHEN ISNULL(combined.DisableSendEnabled, 0) = 1 - OR ISNULL(combined.SendOptionsEnabled, 0) = 1 - THEN 1 ELSE 0 END, - -- Build JSON: use ISJSON guard for SendOptions.Data - N'{"disableSend":' + - CASE WHEN ISNULL(combined.DisableSendEnabled, 0) = 1 - THEN N'true' ELSE N'false' END + - N',"disableHideEmail":' + - CASE WHEN combined.SendOptionsData IS NOT NULL - AND ISJSON(combined.SendOptionsData) = 1 - AND JSON_VALUE(combined.SendOptionsData, '$.disableHideEmail') = 'true' - THEN N'true' ELSE N'false' END + - N'}', - GETUTCDATE(), - GETUTCDATE() - FROM ( - SELECT DISTINCT - COALESCE(ds.OrganizationId, so.OrganizationId) AS OrganizationId, - ds.Enabled AS DisableSendEnabled, - so.Enabled AS SendOptionsEnabled, - so.Data AS SendOptionsData - FROM - [dbo].[Policy] ds - LEFT JOIN - [dbo].[Policy] so - ON ds.OrganizationId = so.OrganizationId - AND so.Type = @SendOptionsType - WHERE - ds.Type = @DisableSendType - UNION - SELECT - so.OrganizationId, - NULL, - so.Enabled, - so.Data - FROM - [dbo].[Policy] so - WHERE - so.Type = @SendOptionsType - AND NOT EXISTS ( - SELECT - 1 - FROM - [dbo].[Policy] ds - WHERE - ds.OrganizationId = so.OrganizationId - AND ds.Type = @DisableSendType - ) - ) combined - -- Skip orgs that already have a SendControls row - WHERE NOT EXISTS ( - SELECT 1 FROM [dbo].[Policy] sc - WHERE sc.OrganizationId = combined.OrganizationId - AND sc.Type = @SendControlsType - ); - - SET @RowsAffected = @@ROWCOUNT; -END diff --git a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs deleted file mode 100644 index db3d4ed1667c..000000000000 --- a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ /dev/null @@ -1,3568 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Bit.MySqlMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20260306000000_CreateSendControlsPolicies")] - partial class CreateSendControlsPolicies - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("CollectionName") - .HasColumnType("longtext"); - - b.Property("Email") - .HasColumnType("longtext"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("GroupName") - .HasColumnType("longtext"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.Property("ResetPasswordKey") - .HasColumnType("longtext"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("UserGuid") - .HasColumnType("char(36)"); - - b.Property("UserName") - .HasColumnType("longtext"); - - b.Property("UsesKeyConnector") - .HasColumnType("tinyint(1)"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("tinyint(1)") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("varchar(2)"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("LimitCollectionCreation") - .HasColumnType("tinyint(1)"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("tinyint(1)"); - - b.Property("LimitItemDeletion") - .HasColumnType("tinyint(1)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("int"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("int"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("int"); - - b.Property("MaxCollections") - .HasColumnType("smallint"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("datetime(6)"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("PrivateKey") - .HasColumnType("longtext"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("ReferenceData") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Seats") - .HasColumnType("int"); - - b.Property("SelfHost") - .HasColumnType("tinyint(1)"); - - b.Property("SmSeats") - .HasColumnType("int"); - - b.Property("SmServiceAccounts") - .HasColumnType("int"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("SyncSeats") - .HasColumnType("tinyint(1)"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("Use2fa") - .HasColumnType("tinyint(1)"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("tinyint(1)"); - - b.Property("UseApi") - .HasColumnType("tinyint(1)"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("tinyint(1)"); - - b.Property("UseCustomPermissions") - .HasColumnType("tinyint(1)"); - - b.Property("UseDirectory") - .HasColumnType("tinyint(1)"); - - b.Property("UseDisableSmAdsForUsers") - .HasColumnType("tinyint(1)"); - - b.Property("UseEvents") - .HasColumnType("tinyint(1)"); - - b.Property("UseGroups") - .HasColumnType("tinyint(1)"); - - b.Property("UseKeyConnector") - .HasColumnType("tinyint(1)"); - - b.Property("UseMyItems") - .HasColumnType("tinyint(1)"); - - b.Property("UseOrganizationDomains") - .HasColumnType("tinyint(1)"); - - b.Property("UsePasswordManager") - .HasColumnType("tinyint(1)"); - - b.Property("UsePhishingBlocker") - .HasColumnType("tinyint(1)"); - - b.Property("UsePolicies") - .HasColumnType("tinyint(1)"); - - b.Property("UseResetPassword") - .HasColumnType("tinyint(1)"); - - b.Property("UseRiskInsights") - .HasColumnType("tinyint(1)"); - - b.Property("UseScim") - .HasColumnType("tinyint(1)"); - - b.Property("UseSecretsManager") - .HasColumnType("tinyint(1)"); - - b.Property("UseSso") - .HasColumnType("tinyint(1)"); - - b.Property("UseTotp") - .HasColumnType("tinyint(1)"); - - b.Property("UsersGetPremium") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled") - .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("BillingEmail") - .HasColumnType("longtext"); - - b.Property("BillingPhone") - .HasColumnType("longtext"); - - b.Property("BusinessAddress1") - .HasColumnType("longtext"); - - b.Property("BusinessAddress2") - .HasColumnType("longtext"); - - b.Property("BusinessAddress3") - .HasColumnType("longtext"); - - b.Property("BusinessCountry") - .HasColumnType("longtext"); - - b.Property("BusinessName") - .HasColumnType("longtext"); - - b.Property("BusinessTaxNumber") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DiscountId") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasColumnType("longtext"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("longtext"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UseEvents") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Settings") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasColumnType("longtext"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("Permissions") - .HasColumnType("longtext"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("varchar(25)"); - - b.Property("Approved") - .HasColumnType("tinyint(1)"); - - b.Property("AuthenticationDate") - .HasColumnType("datetime(6)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("MasterPasswordHash") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("RequestDeviceType") - .HasColumnType("tinyint unsigned"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("ResponseDate") - .HasColumnType("datetime(6)"); - - b.Property("ResponseDeviceId") - .HasColumnType("char(36)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("GranteeId") - .HasColumnType("char(36)"); - - b.Property("GrantorId") - .HasColumnType("char(36)"); - - b.Property("KeyEncrypted") - .HasColumnType("longtext"); - - b.Property("LastNotificationDate") - .HasColumnType("datetime(6)"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("WaitTimeDays") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("GranteeId"); - - b.HasIndex("GrantorId"); - - b.ToTable("EmergencyAccess", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("ConsumedDate") - .HasColumnType("datetime(6)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AaGuid") - .HasColumnType("char(36)"); - - b.Property("Counter") - .HasColumnType("int"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SupportsPrf") - .HasColumnType("tinyint(1)"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("varchar(20)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("int"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Seats") - .HasColumnType("int"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("InstallationId") - .HasColumnType("char(36)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AssignedSeats") - .HasColumnType("int"); - - b.Property("ClientId") - .HasColumnType("char(36)"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Created") - .HasColumnType("datetime(6)"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Total") - .HasColumnType("decimal(65,30)"); - - b.Property("UsedSeats") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AllocatedSeats") - .HasColumnType("int"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("PurchasedSeats") - .HasColumnType("int"); - - b.Property("SeatMinimum") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AmountOff") - .HasColumnType("bigint"); - - b.Property("AudienceType") - .HasColumnType("int"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Currency") - .HasMaxLength(10) - .HasColumnType("varchar(10)"); - - b.Property("Duration") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("varchar(20)"); - - b.Property("DurationInMonths") - .HasColumnType("int"); - - b.Property("EndDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("PercentOff") - .HasPrecision(5, 2) - .HasColumnType("decimal(5,2)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("StartDate") - .HasColumnType("datetime(6)"); - - b.Property("StripeCouponId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("StripeProductIds") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("StripeCouponId") - .IsUnique(); - - b.HasIndex("StartDate", "EndDate") - .HasDatabaseName("IX_SubscriptionDiscount_DateRange") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SubscriptionDiscount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Applications") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Configuration") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Configuration") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EventType") - .HasColumnType("int"); - - b.Property("Filters") - .HasColumnType("longtext"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Template") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("int"); - - b.Property("ApplicationCount") - .HasColumnType("int"); - - b.Property("ApplicationData") - .HasColumnType("longtext"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalApplicationCount") - .HasColumnType("int"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalMemberCount") - .HasColumnType("int"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalPasswordCount") - .HasColumnType("int"); - - b.Property("MemberAtRiskCount") - .HasColumnType("int"); - - b.Property("MemberCount") - .HasColumnType("int"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("int"); - - b.Property("PasswordCount") - .HasColumnType("int"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SummaryData") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Uri") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("varchar(449)"); - - b.Property("AbsoluteExpiration") - .HasColumnType("datetime(6)"); - - b.Property("ExpiresAtTime") - .HasColumnType("datetime(6)"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("bigint"); - - b.Property("Value") - .IsRequired() - .HasColumnType("longblob"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("longtext"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("char(36)"); - - b.Property("Active") - .HasColumnType("tinyint(1)") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("longtext"); - - b.Property("EncryptedPublicKey") - .HasColumnType("longtext"); - - b.Property("EncryptedUserKey") - .HasColumnType("longtext"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("varchar(255)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ActingUserId") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("Date") - .HasColumnType("datetime(6)"); - - b.Property("DeviceType") - .HasColumnType("tinyint unsigned"); - - b.Property("DomainName") - .HasColumnType("longtext"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("InstallationId") - .HasColumnType("char(36)"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("PolicyId") - .HasColumnType("char(36)"); - - b.Property("ProjectId") - .HasColumnType("char(36)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("ProviderOrganizationId") - .HasColumnType("char(36)"); - - b.Property("ProviderUserId") - .HasColumnType("char(36)"); - - b.Property("SecretId") - .HasColumnType("char(36)"); - - b.Property("ServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("SystemUser") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Config") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("varchar(255)"); - - b.Property("JobRunCount") - .HasColumnType("int"); - - b.Property("LastCheckedDate") - .HasColumnType("datetime(6)"); - - b.Property("NextRunDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("VerifiedDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("IsAdminInitiated") - .HasColumnType("tinyint(1)"); - - b.Property("LastSyncDate") - .HasColumnType("datetime(6)"); - - b.Property("Notes") - .HasColumnType("longtext"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("PlanSponsorshipType") - .HasColumnType("tinyint unsigned"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("char(36)"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("char(36)"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("ToDelete") - .HasColumnType("tinyint(1)"); - - b.Property("ValidUntil") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessSecretsManager") - .HasColumnType("tinyint(1)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Permissions") - .HasColumnType("longtext"); - - b.Property("ResetPasswordKey") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PlayId") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("PlayId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PlayItem", null, t => - { - t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); - }); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessCount") - .HasColumnType("int"); - - b.Property("AuthType") - .HasColumnType("tinyint unsigned"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("DeletionDate") - .HasColumnType("datetime(6)"); - - b.Property("Disabled") - .HasColumnType("tinyint(1)"); - - b.Property("Emails") - .HasMaxLength(4000) - .HasColumnType("varchar(4000)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("HideEmail") - .HasColumnType("tinyint(1)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("MaxAccessCount") - .HasColumnType("int"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("varchar(40)"); - - b.Property("Active") - .HasColumnType("tinyint(1)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar(10)"); - - b.Property("Rate") - .HasColumnType("decimal(65,30)"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("varchar(2)"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Amount") - .HasColumnType("decimal(65,30)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PaymentMethodType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Refunded") - .HasColumnType("tinyint(1)"); - - b.Property("RefundedAmount") - .HasColumnType("decimal(65,30)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccountRevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("varchar(7)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar(10)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("EmailVerified") - .HasColumnType("tinyint(1)"); - - b.Property("EquivalentDomains") - .HasColumnType("longtext"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("longtext"); - - b.Property("FailedLoginCount") - .HasColumnType("int"); - - b.Property("ForcePasswordReset") - .HasColumnType("tinyint(1)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Kdf") - .HasColumnType("tinyint unsigned"); - - b.Property("KdfIterations") - .HasColumnType("int"); - - b.Property("KdfMemory") - .HasColumnType("int"); - - b.Property("KdfParallelism") - .HasColumnType("int"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("LastEmailChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LastFailedLoginDate") - .HasColumnType("datetime(6)"); - - b.Property("LastKdfChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LastKeyRotationDate") - .HasColumnType("datetime(6)"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Premium") - .HasColumnType("tinyint(1)"); - - b.Property("PremiumExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("PrivateKey") - .HasColumnType("longtext"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("ReferenceData") - .HasColumnType("longtext"); - - b.Property("RenewalReminderDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("SecurityState") - .HasColumnType("longtext"); - - b.Property("SecurityVersion") - .HasColumnType("int"); - - b.Property("SignedPublicKey") - .HasColumnType("longtext"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("varchar(32)"); - - b.Property("UsesKeyConnector") - .HasColumnType("tinyint(1)"); - - b.Property("VerifyDevices") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SignatureAlgorithm") - .HasColumnType("tinyint unsigned"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("varchar(3000)"); - - b.Property("ClientType") - .HasColumnType("tinyint unsigned"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Global") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Priority") - .HasColumnType("tinyint unsigned"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("TaskId") - .HasColumnType("char(36)"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("char(36)"); - - b.Property("NotificationId") - .HasColumnType("char(36)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("ReadDate") - .HasColumnType("datetime(6)"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("varchar(150)"); - - b.Property("LastActivityDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("varchar(34)"); - - b.Property("Read") - .HasColumnType("tinyint(1)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Write") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("varchar(128)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("varchar(4000)"); - - b.Property("ExpireAt") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("varchar(4000)"); - - b.Property("ServiceAccountId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("Note") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Value") - .HasColumnType("longtext"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("EditorServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("SecretId") - .HasColumnType("char(36)"); - - b.Property("Value") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("VersionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Archives") - .HasColumnType("longtext"); - - b.Property("Attachments") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Favorites") - .HasColumnType("longtext"); - - b.Property("Folders") - .HasColumnType("longtext"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Reprompt") - .HasColumnType("tinyint unsigned"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("char(36)"); - - b.Property("SecretsId") - .HasColumnType("char(36)"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs b/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs deleted file mode 100644 index 1f3b0ae2999e..000000000000 --- a/util/MySqlMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.MySqlMigrations.Migrations; - -/// -public partial class CreateSendControlsPolicies : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - -- Insert for orgs that have SendOptions (with or without DisableSend) - INSERT INTO `Policy` (`Id`, `OrganizationId`, `Type`, `Enabled`, `Data`, `CreationDate`, `RevisionDate`) - SELECT UUID(), - COALESCE(ds.`OrganizationId`, so.`OrganizationId`), - 20, - IF(IFNULL(ds.`Enabled`, 0) = 1 OR IFNULL(so.`Enabled`, 0) = 1, 1, 0), - CONCAT('{""disableSend"":', - IF(IFNULL(ds.`Enabled`, 0) = 1, 'true', 'false'), - ',""disableHideEmail"":', - IF(so.`Data` IS NOT NULL AND JSON_VALID(so.`Data`) - AND JSON_EXTRACT(so.`Data`, '$.disableHideEmail') = true, - 'true', 'false'), - '}'), - UTC_TIMESTAMP(), - UTC_TIMESTAMP() - FROM (SELECT `OrganizationId`, `Enabled`, `Data` FROM `Policy` WHERE `Type` = 7) so - LEFT JOIN (SELECT `OrganizationId`, `Enabled` FROM `Policy` WHERE `Type` = 6) ds - ON ds.`OrganizationId` = so.`OrganizationId` - WHERE NOT EXISTS ( - SELECT 1 FROM `Policy` sc - WHERE sc.`OrganizationId` = COALESCE(ds.`OrganizationId`, so.`OrganizationId`) - AND sc.`Type` = 20 - ); - - -- Insert for orgs that have DisableSend ONLY (no SendOptions) - INSERT INTO `Policy` (`Id`, `OrganizationId`, `Type`, `Enabled`, `Data`, `CreationDate`, `RevisionDate`) - SELECT UUID(), - ds.`OrganizationId`, - 20, - ds.`Enabled`, - CONCAT('{""disableSend"":', IF(ds.`Enabled` = 1, 'true', 'false'), ',""disableHideEmail"":false}'), - UTC_TIMESTAMP(), - UTC_TIMESTAMP() - FROM (SELECT `OrganizationId`, `Enabled` FROM `Policy` WHERE `Type` = 6) ds - WHERE ds.`OrganizationId` NOT IN (SELECT `OrganizationId` FROM `Policy` WHERE `Type` = 7) - AND NOT EXISTS ( - SELECT 1 FROM `Policy` sc - WHERE sc.`OrganizationId` = ds.`OrganizationId` - AND sc.`Type` = 20 - ); - "); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - DELETE FROM `Policy` WHERE `Type` = 20; - "); - } -} diff --git a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs deleted file mode 100644 index aeb6b3041434..000000000000 --- a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ /dev/null @@ -1,3574 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Bit.PostgresMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20260306000000_CreateSendControlsPolicies")] - partial class CreateSendControlsPolicies - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") - .HasAnnotation("ProductVersion", "8.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("CollectionName") - .HasColumnType("text"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("GroupName") - .HasColumnType("text"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.Property("ResetPasswordKey") - .HasColumnType("text"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("UserGuid") - .HasColumnType("uuid"); - - b.Property("UserName") - .HasColumnType("text"); - - b.Property("UsesKeyConnector") - .HasColumnType("boolean"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("LimitCollectionCreation") - .HasColumnType("boolean"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("boolean"); - - b.Property("LimitItemDeletion") - .HasColumnType("boolean"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("integer"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("integer"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("integer"); - - b.Property("MaxCollections") - .HasColumnType("smallint"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("timestamp with time zone"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("PrivateKey") - .HasColumnType("text"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("ReferenceData") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Seats") - .HasColumnType("integer"); - - b.Property("SelfHost") - .HasColumnType("boolean"); - - b.Property("SmSeats") - .HasColumnType("integer"); - - b.Property("SmServiceAccounts") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("SyncSeats") - .HasColumnType("boolean"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("Use2fa") - .HasColumnType("boolean"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("boolean"); - - b.Property("UseApi") - .HasColumnType("boolean"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("boolean"); - - b.Property("UseCustomPermissions") - .HasColumnType("boolean"); - - b.Property("UseDirectory") - .HasColumnType("boolean"); - - b.Property("UseDisableSmAdsForUsers") - .HasColumnType("boolean"); - - b.Property("UseEvents") - .HasColumnType("boolean"); - - b.Property("UseGroups") - .HasColumnType("boolean"); - - b.Property("UseKeyConnector") - .HasColumnType("boolean"); - - b.Property("UseMyItems") - .HasColumnType("boolean"); - - b.Property("UseOrganizationDomains") - .HasColumnType("boolean"); - - b.Property("UsePasswordManager") - .HasColumnType("boolean"); - - b.Property("UsePhishingBlocker") - .HasColumnType("boolean"); - - b.Property("UsePolicies") - .HasColumnType("boolean"); - - b.Property("UseResetPassword") - .HasColumnType("boolean"); - - b.Property("UseRiskInsights") - .HasColumnType("boolean"); - - b.Property("UseScim") - .HasColumnType("boolean"); - - b.Property("UseSecretsManager") - .HasColumnType("boolean"); - - b.Property("UseSso") - .HasColumnType("boolean"); - - b.Property("UseTotp") - .HasColumnType("boolean"); - - b.Property("UsersGetPremium") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled"); - - NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("BillingEmail") - .HasColumnType("text"); - - b.Property("BillingPhone") - .HasColumnType("text"); - - b.Property("BusinessAddress1") - .HasColumnType("text"); - - b.Property("BusinessAddress2") - .HasColumnType("text"); - - b.Property("BusinessAddress3") - .HasColumnType("text"); - - b.Property("BusinessCountry") - .HasColumnType("text"); - - b.Property("BusinessName") - .HasColumnType("text"); - - b.Property("BusinessTaxNumber") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DiscountId") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasColumnType("text"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UseEvents") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Settings") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Permissions") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("character varying(25)"); - - b.Property("Approved") - .HasColumnType("boolean"); - - b.Property("AuthenticationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("MasterPasswordHash") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("RequestDeviceType") - .HasColumnType("smallint"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ResponseDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ResponseDeviceId") - .HasColumnType("uuid"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("GranteeId") - .HasColumnType("uuid"); - - b.Property("GrantorId") - .HasColumnType("uuid"); - - b.Property("KeyEncrypted") - .HasColumnType("text"); - - b.Property("LastNotificationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("WaitTimeDays") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("GranteeId"); - - b.HasIndex("GrantorId"); - - b.ToTable("EmergencyAccess", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ConsumedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AaGuid") - .HasColumnType("uuid"); - - b.Property("Counter") - .HasColumnType("integer"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SupportsPrf") - .HasColumnType("boolean"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("integer"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Seats") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("InstallationId") - .HasColumnType("uuid"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssignedSeats") - .HasColumnType("integer"); - - b.Property("ClientId") - .HasColumnType("uuid"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Total") - .HasColumnType("numeric"); - - b.Property("UsedSeats") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AllocatedSeats") - .HasColumnType("integer"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("PurchasedSeats") - .HasColumnType("integer"); - - b.Property("SeatMinimum") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AmountOff") - .HasColumnType("bigint"); - - b.Property("AudienceType") - .HasColumnType("integer"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Currency") - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("Duration") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("DurationInMonths") - .HasColumnType("integer"); - - b.Property("EndDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PercentOff") - .HasPrecision(5, 2) - .HasColumnType("numeric(5,2)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("StartDate") - .HasColumnType("timestamp with time zone"); - - b.Property("StripeCouponId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("StripeProductIds") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("StripeCouponId") - .IsUnique(); - - b.HasIndex("StartDate", "EndDate") - .HasDatabaseName("IX_SubscriptionDiscount_DateRange") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SubscriptionDiscount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Applications") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Configuration") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Configuration") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EventType") - .HasColumnType("integer"); - - b.Property("Filters") - .HasColumnType("text"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Template") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("integer"); - - b.Property("ApplicationCount") - .HasColumnType("integer"); - - b.Property("ApplicationData") - .HasColumnType("text"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalApplicationCount") - .HasColumnType("integer"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalMemberCount") - .HasColumnType("integer"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalPasswordCount") - .HasColumnType("integer"); - - b.Property("MemberAtRiskCount") - .HasColumnType("integer"); - - b.Property("MemberCount") - .HasColumnType("integer"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("integer"); - - b.Property("PasswordCount") - .HasColumnType("integer"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SummaryData") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Uri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("character varying(449)"); - - b.Property("AbsoluteExpiration") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpiresAtTime") - .HasColumnType("timestamp with time zone"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("bigint"); - - b.Property("Value") - .IsRequired() - .HasColumnType("bytea"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("text"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("text"); - - b.Property("EncryptedPublicKey") - .HasColumnType("text"); - - b.Property("EncryptedUserKey") - .HasColumnType("text"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ActingUserId") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("Date") - .HasColumnType("timestamp with time zone"); - - b.Property("DeviceType") - .HasColumnType("smallint"); - - b.Property("DomainName") - .HasColumnType("text"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("uuid"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("InstallationId") - .HasColumnType("uuid"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("PolicyId") - .HasColumnType("uuid"); - - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("ProviderOrganizationId") - .HasColumnType("uuid"); - - b.Property("ProviderUserId") - .HasColumnType("uuid"); - - b.Property("SecretId") - .HasColumnType("uuid"); - - b.Property("ServiceAccountId") - .HasColumnType("uuid"); - - b.Property("SystemUser") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Config") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("JobRunCount") - .HasColumnType("integer"); - - b.Property("LastCheckedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("NextRunDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("text"); - - b.Property("VerifiedDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("IsAdminInitiated") - .HasColumnType("boolean"); - - b.Property("LastSyncDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Notes") - .HasColumnType("text"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PlanSponsorshipType") - .HasColumnType("smallint"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("uuid"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("uuid"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("uuid"); - - b.Property("ToDelete") - .HasColumnType("boolean"); - - b.Property("ValidUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessSecretsManager") - .HasColumnType("boolean"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Permissions") - .HasColumnType("text"); - - b.Property("ResetPasswordKey") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PlayId") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("PlayId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PlayItem", null, t => - { - t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); - }); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessCount") - .HasColumnType("integer"); - - b.Property("AuthType") - .HasColumnType("smallint"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("DeletionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Disabled") - .HasColumnType("boolean"); - - b.Property("Emails") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("HideEmail") - .HasColumnType("boolean"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("MaxAccessCount") - .HasColumnType("integer"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("Rate") - .HasColumnType("numeric"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Amount") - .HasColumnType("numeric"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PaymentMethodType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Refunded") - .HasColumnType("boolean"); - - b.Property("RefundedAmount") - .HasColumnType("numeric"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccountRevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("character varying(7)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("EmailVerified") - .HasColumnType("boolean"); - - b.Property("EquivalentDomains") - .HasColumnType("text"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("text"); - - b.Property("FailedLoginCount") - .HasColumnType("integer"); - - b.Property("ForcePasswordReset") - .HasColumnType("boolean"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Kdf") - .HasColumnType("smallint"); - - b.Property("KdfIterations") - .HasColumnType("integer"); - - b.Property("KdfMemory") - .HasColumnType("integer"); - - b.Property("KdfParallelism") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("LastEmailChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastFailedLoginDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastKdfChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastKeyRotationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Premium") - .HasColumnType("boolean"); - - b.Property("PremiumExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivateKey") - .HasColumnType("text"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("ReferenceData") - .HasColumnType("text"); - - b.Property("RenewalReminderDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("SecurityState") - .HasColumnType("text"); - - b.Property("SecurityVersion") - .HasColumnType("integer"); - - b.Property("SignedPublicKey") - .HasColumnType("text"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("UsesKeyConnector") - .HasColumnType("boolean"); - - b.Property("VerifyDevices") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SignatureAlgorithm") - .HasColumnType("smallint"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("character varying(3000)"); - - b.Property("ClientType") - .HasColumnType("smallint"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Global") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Priority") - .HasColumnType("smallint"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("TaskId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("NotificationId") - .HasColumnType("uuid"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ReadDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.Property("LastActivityDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("character varying(34)"); - - b.Property("Read") - .HasColumnType("boolean"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Write") - .HasColumnType("boolean"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.Property("ServiceAccountId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("uuid"); - - b.Property("EditorServiceAccountId") - .HasColumnType("uuid"); - - b.Property("SecretId") - .HasColumnType("uuid"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.Property("VersionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Archives") - .HasColumnType("text"); - - b.Property("Attachments") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Favorites") - .HasColumnType("text"); - - b.Property("Folders") - .HasColumnType("text"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Reprompt") - .HasColumnType("smallint"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("uuid"); - - b.Property("SecretsId") - .HasColumnType("uuid"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs b/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs deleted file mode 100644 index a1fe93cdc976..000000000000 --- a/util/PostgresMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.PostgresMigrations.Migrations; - -/// -public partial class CreateSendControlsPolicies : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") - SELECT gen_random_uuid(), - COALESCE(ds.""OrganizationId"", so.""OrganizationId""), - 20, - (COALESCE(ds.""Enabled"", false) OR COALESCE(so.""Enabled"", false)), - jsonb_build_object( - 'disableSend', COALESCE(ds.""Enabled"", false), - 'disableHideEmail', - CASE WHEN so.""Data"" IS NOT NULL - AND so.""Data"" ~ '^\{.*\}$' - THEN COALESCE((so.""Data""::jsonb ->> 'disableHideEmail')::boolean, false) - ELSE false END - )::text, - NOW() AT TIME ZONE 'UTC', - NOW() AT TIME ZONE 'UTC' - FROM ( - SELECT ds2.""OrganizationId"", ds2.""Enabled"" - FROM ""Policy"" ds2 WHERE ds2.""Type"" = 6 - ) ds - FULL OUTER JOIN ( - SELECT so2.""OrganizationId"", so2.""Enabled"", so2.""Data"" - FROM ""Policy"" so2 WHERE so2.""Type"" = 7 - ) so ON ds.""OrganizationId"" = so.""OrganizationId"" - WHERE NOT EXISTS ( - SELECT 1 FROM ""Policy"" sc - WHERE sc.""OrganizationId"" = COALESCE(ds.""OrganizationId"", so.""OrganizationId"") - AND sc.""Type"" = 20 - ); - "); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - DELETE FROM ""Policy"" WHERE ""Type"" = 20; - "); - } -} diff --git a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs deleted file mode 100644 index e0e7b4601c0b..000000000000 --- a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.Designer.cs +++ /dev/null @@ -1,3557 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Bit.SqliteMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20260306000000_CreateSendControlsPolicies")] - partial class CreateSendControlsPolicies - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("CollectionName") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("GroupName") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.Property("ResetPasswordKey") - .HasColumnType("TEXT"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("UserGuid") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.Property("UsesKeyConnector") - .HasColumnType("INTEGER"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("INTEGER") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("TEXT"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("LimitCollectionCreation") - .HasColumnType("INTEGER"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("INTEGER"); - - b.Property("LimitItemDeletion") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("INTEGER"); - - b.Property("MaxCollections") - .HasColumnType("INTEGER"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("TEXT"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("PrivateKey") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("ReferenceData") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Seats") - .HasColumnType("INTEGER"); - - b.Property("SelfHost") - .HasColumnType("INTEGER"); - - b.Property("SmSeats") - .HasColumnType("INTEGER"); - - b.Property("SmServiceAccounts") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Storage") - .HasColumnType("INTEGER"); - - b.Property("SyncSeats") - .HasColumnType("INTEGER"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("Use2fa") - .HasColumnType("INTEGER"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("INTEGER"); - - b.Property("UseApi") - .HasColumnType("INTEGER"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("INTEGER"); - - b.Property("UseCustomPermissions") - .HasColumnType("INTEGER"); - - b.Property("UseDirectory") - .HasColumnType("INTEGER"); - - b.Property("UseDisableSmAdsForUsers") - .HasColumnType("INTEGER"); - - b.Property("UseEvents") - .HasColumnType("INTEGER"); - - b.Property("UseGroups") - .HasColumnType("INTEGER"); - - b.Property("UseKeyConnector") - .HasColumnType("INTEGER"); - - b.Property("UseMyItems") - .HasColumnType("INTEGER"); - - b.Property("UseOrganizationDomains") - .HasColumnType("INTEGER"); - - b.Property("UsePasswordManager") - .HasColumnType("INTEGER"); - - b.Property("UsePhishingBlocker") - .HasColumnType("INTEGER"); - - b.Property("UsePolicies") - .HasColumnType("INTEGER"); - - b.Property("UseResetPassword") - .HasColumnType("INTEGER"); - - b.Property("UseRiskInsights") - .HasColumnType("INTEGER"); - - b.Property("UseScim") - .HasColumnType("INTEGER"); - - b.Property("UseSecretsManager") - .HasColumnType("INTEGER"); - - b.Property("UseSso") - .HasColumnType("INTEGER"); - - b.Property("UseTotp") - .HasColumnType("INTEGER"); - - b.Property("UsersGetPremium") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled") - .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("BillingEmail") - .HasColumnType("TEXT"); - - b.Property("BillingPhone") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress1") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress2") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress3") - .HasColumnType("TEXT"); - - b.Property("BusinessCountry") - .HasColumnType("TEXT"); - - b.Property("BusinessName") - .HasColumnType("TEXT"); - - b.Property("BusinessTaxNumber") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DiscountId") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UseEvents") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Settings") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Permissions") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("TEXT"); - - b.Property("Approved") - .HasColumnType("INTEGER"); - - b.Property("AuthenticationDate") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("MasterPasswordHash") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("RequestDeviceType") - .HasColumnType("INTEGER"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("ResponseDate") - .HasColumnType("TEXT"); - - b.Property("ResponseDeviceId") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("GranteeId") - .HasColumnType("TEXT"); - - b.Property("GrantorId") - .HasColumnType("TEXT"); - - b.Property("KeyEncrypted") - .HasColumnType("TEXT"); - - b.Property("LastNotificationDate") - .HasColumnType("TEXT"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("WaitTimeDays") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("GranteeId"); - - b.HasIndex("GrantorId"); - - b.ToTable("EmergencyAccess", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("ConsumedDate") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AaGuid") - .HasColumnType("TEXT"); - - b.Property("Counter") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SupportsPrf") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Seats") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("InstallationId") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AssignedSeats") - .HasColumnType("INTEGER"); - - b.Property("ClientId") - .HasColumnType("TEXT"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Created") - .HasColumnType("TEXT"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Total") - .HasColumnType("TEXT"); - - b.Property("UsedSeats") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AllocatedSeats") - .HasColumnType("INTEGER"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("PurchasedSeats") - .HasColumnType("INTEGER"); - - b.Property("SeatMinimum") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.SubscriptionDiscount", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AmountOff") - .HasColumnType("INTEGER"); - - b.Property("AudienceType") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Currency") - .HasMaxLength(10) - .HasColumnType("TEXT"); - - b.Property("Duration") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("TEXT"); - - b.Property("DurationInMonths") - .HasColumnType("INTEGER"); - - b.Property("EndDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("PercentOff") - .HasPrecision(5, 2) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("StartDate") - .HasColumnType("TEXT"); - - b.Property("StripeCouponId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("StripeProductIds") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("StripeCouponId") - .IsUnique(); - - b.HasIndex("StartDate", "EndDate") - .HasDatabaseName("IX_SubscriptionDiscount_DateRange") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SubscriptionDiscount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Applications") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Configuration") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Configuration") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EventType") - .HasColumnType("INTEGER"); - - b.Property("Filters") - .HasColumnType("TEXT"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Template") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationData") - .HasColumnType("TEXT"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalApplicationCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalMemberCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalPasswordCount") - .HasColumnType("INTEGER"); - - b.Property("MemberAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("MemberCount") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("PasswordCount") - .HasColumnType("INTEGER"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SummaryData") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Uri") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("TEXT"); - - b.Property("AbsoluteExpiration") - .HasColumnType("TEXT"); - - b.Property("ExpiresAtTime") - .HasColumnType("TEXT"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("INTEGER"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Active") - .HasColumnType("INTEGER") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("TEXT"); - - b.Property("EncryptedPublicKey") - .HasColumnType("TEXT"); - - b.Property("EncryptedUserKey") - .HasColumnType("TEXT"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ActingUserId") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("Date") - .HasColumnType("TEXT"); - - b.Property("DeviceType") - .HasColumnType("INTEGER"); - - b.Property("DomainName") - .HasColumnType("TEXT"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("InstallationId") - .HasColumnType("TEXT"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("PolicyId") - .HasColumnType("TEXT"); - - b.Property("ProjectId") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("ProviderOrganizationId") - .HasColumnType("TEXT"); - - b.Property("ProviderUserId") - .HasColumnType("TEXT"); - - b.Property("SecretId") - .HasColumnType("TEXT"); - - b.Property("ServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("SystemUser") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Config") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("TEXT"); - - b.Property("JobRunCount") - .HasColumnType("INTEGER"); - - b.Property("LastCheckedDate") - .HasColumnType("TEXT"); - - b.Property("NextRunDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("VerifiedDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("IsAdminInitiated") - .HasColumnType("INTEGER"); - - b.Property("LastSyncDate") - .HasColumnType("TEXT"); - - b.Property("Notes") - .HasColumnType("TEXT"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PlanSponsorshipType") - .HasColumnType("INTEGER"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("TEXT"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("TEXT"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("ToDelete") - .HasColumnType("INTEGER"); - - b.Property("ValidUntil") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessSecretsManager") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Permissions") - .HasColumnType("TEXT"); - - b.Property("ResetPasswordKey") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PlayId") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("PlayId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PlayItem", null, t => - { - t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); - }); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessCount") - .HasColumnType("INTEGER"); - - b.Property("AuthType") - .HasColumnType("INTEGER"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("DeletionDate") - .HasColumnType("TEXT"); - - b.Property("Disabled") - .HasColumnType("INTEGER"); - - b.Property("Emails") - .HasMaxLength(4000) - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("HideEmail") - .HasColumnType("INTEGER"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("MaxAccessCount") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("TEXT"); - - b.Property("Active") - .HasColumnType("INTEGER"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("TEXT"); - - b.Property("Rate") - .HasColumnType("TEXT"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Amount") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PaymentMethodType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Refunded") - .HasColumnType("INTEGER"); - - b.Property("RefundedAmount") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountRevisionDate") - .HasColumnType("TEXT"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("TEXT"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailVerified") - .HasColumnType("INTEGER"); - - b.Property("EquivalentDomains") - .HasColumnType("TEXT"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("TEXT"); - - b.Property("FailedLoginCount") - .HasColumnType("INTEGER"); - - b.Property("ForcePasswordReset") - .HasColumnType("INTEGER"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Kdf") - .HasColumnType("INTEGER"); - - b.Property("KdfIterations") - .HasColumnType("INTEGER"); - - b.Property("KdfMemory") - .HasColumnType("INTEGER"); - - b.Property("KdfParallelism") - .HasColumnType("INTEGER"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("LastEmailChangeDate") - .HasColumnType("TEXT"); - - b.Property("LastFailedLoginDate") - .HasColumnType("TEXT"); - - b.Property("LastKdfChangeDate") - .HasColumnType("TEXT"); - - b.Property("LastKeyRotationDate") - .HasColumnType("TEXT"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("TEXT"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Premium") - .HasColumnType("INTEGER"); - - b.Property("PremiumExpirationDate") - .HasColumnType("TEXT"); - - b.Property("PrivateKey") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("ReferenceData") - .HasColumnType("TEXT"); - - b.Property("RenewalReminderDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("SecurityState") - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("SignedPublicKey") - .HasColumnType("TEXT"); - - b.Property("Storage") - .HasColumnType("INTEGER"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("TEXT"); - - b.Property("UsesKeyConnector") - .HasColumnType("INTEGER"); - - b.Property("VerifyDevices") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SignatureAlgorithm") - .HasColumnType("INTEGER"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("TEXT"); - - b.Property("ClientType") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Global") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("TaskId") - .HasColumnType("TEXT"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("NotificationId") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("ReadDate") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("TEXT"); - - b.Property("LastActivityDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - - b.Property("Read") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Write") - .HasColumnType("INTEGER"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("TEXT"); - - b.Property("ExpireAt") - .HasColumnType("TEXT"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("TEXT"); - - b.Property("ServiceAccountId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Note") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("EditorServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("SecretId") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("VersionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Archives") - .HasColumnType("TEXT"); - - b.Property("Attachments") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Favorites") - .HasColumnType("TEXT"); - - b.Property("Folders") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Reprompt") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("TEXT"); - - b.Property("SecretsId") - .HasColumnType("TEXT"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs b/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs deleted file mode 100644 index 379f43120fba..000000000000 --- a/util/SqliteMigrations/Migrations/20260306000000_CreateSendControlsPolicies.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.SqliteMigrations.Migrations; - -/// -public partial class CreateSendControlsPolicies : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - // SQLite does not support FULL OUTER JOIN; use two separate inserts. - // SQLite 3.38+ has json_valid() and json_extract(). - - // Insert for orgs that have SendOptions (with or without DisableSend) - migrationBuilder.Sql(@" - INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") - SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || - substr(lower(hex(randomblob(2))),2) || '-' || - substr('89ab',abs(random()) % 4 + 1, 1) || - substr(lower(hex(randomblob(2))),2) || '-' || lower(hex(randomblob(6))), - COALESCE(ds.""OrganizationId"", so.""OrganizationId""), - 20, - CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 OR IFNULL(so.""Enabled"", 0) = 1 THEN 1 ELSE 0 END, - '{{""disableSend"":' || - CASE WHEN IFNULL(ds.""Enabled"", 0) = 1 THEN 'true' ELSE 'false' END || - ',""disableHideEmail"":' || - CASE WHEN so.""Data"" IS NOT NULL - AND json_valid(so.""Data"") = 1 - AND json_extract(so.""Data"", '$.disableHideEmail') = 1 - THEN 'true' ELSE 'false' END || - '}}', - datetime('now'), - datetime('now') - FROM (SELECT ""OrganizationId"", ""Enabled"", ""Data"" FROM ""Policy"" WHERE ""Type"" = 7) so - LEFT JOIN (SELECT ""OrganizationId"", ""Enabled"" FROM ""Policy"" WHERE ""Type"" = 6) ds - ON ds.""OrganizationId"" = so.""OrganizationId"" - WHERE NOT EXISTS ( - SELECT 1 FROM ""Policy"" sc - WHERE sc.""OrganizationId"" = COALESCE(ds.""OrganizationId"", so.""OrganizationId"") - AND sc.""Type"" = 20 - ); - "); - - // Insert for orgs that have DisableSend ONLY (no SendOptions) - migrationBuilder.Sql(@" - INSERT INTO ""Policy"" (""Id"", ""OrganizationId"", ""Type"", ""Enabled"", ""Data"", ""CreationDate"", ""RevisionDate"") - SELECT lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || - substr(lower(hex(randomblob(2))),2) || '-' || - substr('89ab',abs(random()) % 4 + 1, 1) || - substr(lower(hex(randomblob(2))),2) || '-' || lower(hex(randomblob(6))), - ds.""OrganizationId"", - 20, - ds.""Enabled"", - '{{""disableSend"":' || CASE WHEN ds.""Enabled"" = 1 THEN 'true' ELSE 'false' END || ',""disableHideEmail"":false}}', - datetime('now'), - datetime('now') - FROM (SELECT ""OrganizationId"", ""Enabled"" FROM ""Policy"" WHERE ""Type"" = 6) ds - WHERE ds.""OrganizationId"" NOT IN (SELECT ""OrganizationId"" FROM ""Policy"" WHERE ""Type"" = 7) - AND NOT EXISTS ( - SELECT 1 FROM ""Policy"" sc - WHERE sc.""OrganizationId"" = ds.""OrganizationId"" - AND sc.""Type"" = 20 - ); - "); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - DELETE FROM ""Policy"" WHERE ""Type"" = 20; - "); - } -} From b5449649f2c6e5c27e0f0f9f1d543edf9e020a25 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 18 Mar 2026 08:51:39 -0700 Subject: [PATCH 14/40] whitespacing and comment correction --- .../Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs | 2 +- src/Core/Tools/SendFeatures/Services/SendValidationService.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index 028f8c088b43..6e3486c38444 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -24,7 +24,7 @@ public async Task ExecutePostUpsertSideEffectAsync( { var organizationId = policyRequest.PolicyUpdate.OrganizationId; - // Step 1: sync SendOptionsPolicy.Data.DisableSend -> SendControlsPolicy.Data.DisableSend + // Step 1: sync SendOptionsPolicy.Data.DisableHideEmail -> SendControlsPolicy.Data.DisableHideEmail var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( organizationId, PolicyType.SendControls) ?? new Policy { diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index 0f21ce9f5c6a..017f982b2a10 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -64,8 +64,6 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) { - - if (sendControlsRequirement.DisableSend || disableSendRequirement.DisableSend) { throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send."); From 50fdaf20ca905163201e5c64a2f819d96f1f1151 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:40:56 -0700 Subject: [PATCH 15/40] aggregate kegacy Send policies in PolicyQuery and adjust PoliciesController logic --- .../Controllers/PoliciesController.cs | 28 +++++++++++++- .../Policies/Implementations/PolicyQuery.cs | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 9087cd908741..ccfdfe3cd0f2 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -8,6 +8,7 @@ using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Models.Response; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; @@ -89,8 +90,33 @@ public async Task> GetAll(string orgId) } var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid); + var responses = policies.Select(p => new PolicyResponseModel(p)).ToList(); - return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); + if (policies.Any(p => p.Type == PolicyType.SendControls)) + { + return new ListResponseModel(responses); + } + + var sendControlsStatus = await _policyQuery.RunAsync(orgIdGuid, PolicyType.SendControls); + if (!sendControlsStatus.Enabled) + { + return new ListResponseModel(responses); + } + + var data = sendControlsStatus.GetDataModel(); + responses.Add(new PolicyResponseModel + { + OrganizationId = sendControlsStatus.OrganizationId, + Type = sendControlsStatus.Type, + Enabled = sendControlsStatus.Enabled, + Data = new Dictionary + { + ["disableSend"] = data.DisableSend, + ["disableHideEmail"] = data.DisableHideEmail, + }, + }); + + return new ListResponseModel(responses); } [AllowAnonymous] diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs index 0ee6f9ab06ac..6d6c0027c8d8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; @@ -9,6 +10,43 @@ public class PolicyQuery(IPolicyRepository policyRepository) : IPolicyQuery public async Task RunAsync(Guid organizationId, PolicyType policyType) { var dbPolicy = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, policyType); + + if (dbPolicy == null && policyType == PolicyType.SendControls) + { + return await SynthesizeSendControlsStatusAsync(organizationId); + } + return new PolicyStatus(organizationId, policyType, dbPolicy); } + + /// + /// When no SendControls policy row exists in the database, synthesizes a PolicyStatus + /// from the legacy DisableSend and SendOptions policies. This supports lazy migration + /// from the two legacy policies into the unified SendControls policy without requiring + /// a database migration script. + /// + private async Task SynthesizeSendControlsStatusAsync(Guid organizationId) + { + var disableSendPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + organizationId, PolicyType.DisableSend); + var sendOptionsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( + organizationId, PolicyType.SendOptions); + + var disableSend = disableSendPolicy?.Enabled ?? false; + var disableHideEmail = sendOptionsPolicy?.Enabled == true + && sendOptionsPolicy.GetDataModel().DisableHideEmail; + var enabled = disableSend || disableHideEmail; + + var data = new SendControlsPolicyData + { + DisableSend = disableSend, + DisableHideEmail = disableHideEmail, + }; + + return new PolicyStatus(organizationId, PolicyType.SendControls) + { + Enabled = enabled, + Data = CoreHelpers.ClassToJsonData(data), + }; + } } From 84d0cd17d3d924b39bc2e9ecc501c7d36c381601 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:50:16 -0700 Subject: [PATCH 16/40] add comments to simplify post-migration cleanup --- src/Api/AdminConsole/Controllers/PoliciesController.cs | 3 +++ .../Policies/Implementations/PolicyQuery.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index ccfdfe3cd0f2..92f2558c16cf 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -90,6 +90,9 @@ public async Task> GetAll(string orgId) } var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid); + + // Once migration from legacy Send policies > SendControls has run, replace the rest of this method with: + // return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); var responses = policies.Select(p => new PolicyResponseModel(p)).ToList(); if (policies.Any(p => p.Type == PolicyType.SendControls)) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs index 6d6c0027c8d8..da0bf681c0d0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs @@ -11,6 +11,7 @@ public async Task RunAsync(Guid organizationId, PolicyType policyT { var dbPolicy = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, policyType); + // Remove this block and SynthesizeSendControlsStatusAsync once migration has run if (dbPolicy == null && policyType == PolicyType.SendControls) { return await SynthesizeSendControlsStatusAsync(organizationId); From f27e3589d168380d91b5a6492c5b1771ef0c2e44 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:16:46 -0700 Subject: [PATCH 17/40] consolidate legacy Send policy synthesis from PoliciesController into PolicyQuery.GetAllAsync --- .../Controllers/PoliciesController.cs | 36 +----- .../Organizations/PolicyResponseModel.cs | 13 ++ .../Policies/IPolicyQuery.cs | 11 +- .../Policies/Implementations/PolicyQuery.cs | 18 +++ .../Policies/PolicyQueryTests.cs | 119 ++++++++++++++++++ 5 files changed, 164 insertions(+), 33 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 92f2558c16cf..969a32974e23 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -8,7 +8,6 @@ using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Models.Response; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; @@ -89,37 +88,10 @@ public async Task> GetAll(string orgId) throw new NotFoundException(); } - var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgIdGuid); - - // Once migration from legacy Send policies > SendControls has run, replace the rest of this method with: - // return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); - var responses = policies.Select(p => new PolicyResponseModel(p)).ToList(); - - if (policies.Any(p => p.Type == PolicyType.SendControls)) - { - return new ListResponseModel(responses); - } - - var sendControlsStatus = await _policyQuery.RunAsync(orgIdGuid, PolicyType.SendControls); - if (!sendControlsStatus.Enabled) - { - return new ListResponseModel(responses); - } - - var data = sendControlsStatus.GetDataModel(); - responses.Add(new PolicyResponseModel - { - OrganizationId = sendControlsStatus.OrganizationId, - Type = sendControlsStatus.Type, - Enabled = sendControlsStatus.Enabled, - Data = new Dictionary - { - ["disableSend"] = data.DisableSend, - ["disableHideEmail"] = data.DisableHideEmail, - }, - }); - - return new ListResponseModel(responses); + // Once migration from legacy Send policies > SendControls has run, replace _policyQuery.GetAllAsync + // with a direct _policyRepository.GetManyByOrganizationIdAsync call + var policies = await _policyQuery.GetAllAsync(orgIdGuid); + return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); } [AllowAnonymous] diff --git a/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs index 0507de7a5575..8701a95bace4 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs @@ -4,6 +4,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.Models.Api; namespace Bit.Api.AdminConsole.Models.Response.Organizations; @@ -33,6 +34,18 @@ public PolicyResponseModel(Policy policy, string obj = "policy") RevisionDate = policy.RevisionDate; } + public PolicyResponseModel(PolicyStatus policyStatus, string obj = "policy") + : base(obj) + { + OrganizationId = policyStatus.OrganizationId; + Type = policyStatus.Type; + Enabled = policyStatus.Enabled; + if (!string.IsNullOrWhiteSpace(policyStatus.Data)) + { + Data = JsonSerializer.Deserialize>(policyStatus.Data); + } + } + public Guid Id { get; set; } public Guid OrganizationId { get; set; } public PolicyType Type { get; set; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs index 02eeeaa84760..ac9258ce5e88 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyQuery.cs @@ -11,7 +11,16 @@ public interface IPolicyQuery /// /// This query is the entrypoint for consumers interested in understanding how a particular /// has been applied to an organization; the resultant is not indicative of explicit - /// policy configuration. + /// policy configuration. /// Task RunAsync(Guid organizationId, PolicyType policyType); + + /// + /// Retrieves all policies for an organization. + /// + /// + /// Introduced to isolate the aggregation logic used to construct SendControls policy status + /// from legacy Send policy statuss. May be removed if and when a SendControls migration runs. + /// + Task> GetAllAsync(Guid organizationId); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs index da0bf681c0d0..d87f878c483d 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs @@ -7,6 +7,24 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; public class PolicyQuery(IPolicyRepository policyRepository) : IPolicyQuery { + public async Task> GetAllAsync(Guid organizationId) + { + var policies = await policyRepository.GetManyByOrganizationIdAsync(organizationId); + var results = policies.Select(p => new PolicyStatus(organizationId, p.Type, p)).ToList(); + + // Remove this block once migration from legacy Send policies > SendControls has run + if (policies.All(p => p.Type != PolicyType.SendControls)) + { + var synthesized = await SynthesizeSendControlsStatusAsync(organizationId); + if (synthesized.Enabled) + { + results.Add(synthesized); + } + } + + return results; + } + public async Task RunAsync(Guid organizationId, PolicyType policyType) { var dbPolicy = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, policyType); diff --git a/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs b/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs index ac33a5e5a6a4..58d2404f76a3 100644 --- a/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs +++ b/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs @@ -1,7 +1,9 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -52,4 +54,121 @@ public async Task RunAsync_WithNonExistentPolicy_ReturnsDefaultDisabledPolicy( Assert.False(policyData.Enabled); Assert.Null(policyData.Data); } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithSendControlsInDb_ReturnsAsIs( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange + var sendControlsPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SendControls, + Enabled = true, + Data = CoreHelpers.ClassToJsonData(new SendControlsPolicyData + { + DisableSend = true, + DisableHideEmail = false, + }), + }; + var otherPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SingleOrg, + Enabled = true, + }; + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { sendControlsPolicy, otherPolicy }); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert + Assert.Equal(2, results.Count); + Assert.Contains(results, p => p.Type == PolicyType.SendControls); + Assert.Contains(results, p => p.Type == PolicyType.SingleOrg); + + // Should not attempt to synthesize + // Aggregation is not necessary if DB is already reporting SendControls policy state + await sutProvider.GetDependency() + .DidNotReceive() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend); + } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithoutSendControls_LegacyEnabled_SynthesizesSendControls( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange — legacy DisableSend is enabled, no SendControls row + var disableSendPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.DisableSend, + Enabled = true, + }; + var singleOrgPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SingleOrg, + Enabled = true, + }; + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { disableSendPolicy, singleOrgPolicy }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend) + .Returns(disableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.SendOptions) + .ReturnsNull(); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert — original 2 + synthesized SendControls + Assert.Equal(3, results.Count); + var synthesized = results.Single(p => p.Type == PolicyType.SendControls); + Assert.True(synthesized.Enabled); + var data = synthesized.GetDataModel(); + Assert.True(data.DisableSend); + Assert.False(data.DisableHideEmail); + } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithoutSendControls_NoLegacyEnabled_NoExtraEntry( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange — no SendControls, legacy policies disabled + var singleOrgPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SingleOrg, + Enabled = true, + }; + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { singleOrgPolicy }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend) + .ReturnsNull(); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.SendOptions) + .ReturnsNull(); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert — only the original policy, no synthesized entry + Assert.Single(results); + Assert.Equal(PolicyType.SingleOrg, results[0].Type); + } } From 1f54881562cfdbf7d340cc46ab19f5059d847c94 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:28:02 -0700 Subject: [PATCH 18/40] respond to review comments and other minor fixes --- .../Controllers/PoliciesController.cs | 6 +- .../Organizations/PolicyResponseModel.cs | 13 -- .../Policies/Implementations/PolicyQuery.cs | 10 +- .../DisableSendSyncPolicyEvent.cs | 8 +- .../SendControlsSyncPolicyEvent.cs | 30 ++--- .../SendOptionsSyncPolicyEvent.cs | 6 +- .../Services/SendValidationService.cs | 34 ++---- .../DisableSendSyncPolicyEventTests.cs | 15 +-- .../SendControlsSyncPolicyEventTests.cs | 28 ++--- .../SendOptionsSyncPolicyEventTests.cs | 11 +- .../Policies/PolicyQueryTests.cs | 115 +++++++++++++++++- .../Services/SendValidationServiceTests.cs | 89 -------------- 12 files changed, 167 insertions(+), 198 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index 969a32974e23..27ef89cfc266 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -80,7 +80,7 @@ public async Task Get(Guid orgId, PolicyType type) } [HttpGet("")] - public async Task> GetAll(string orgId) + public async Task> GetAll(string orgId) { var orgIdGuid = new Guid(orgId); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -88,10 +88,8 @@ public async Task> GetAll(string orgId) throw new NotFoundException(); } - // Once migration from legacy Send policies > SendControls has run, replace _policyQuery.GetAllAsync - // with a direct _policyRepository.GetManyByOrganizationIdAsync call var policies = await _policyQuery.GetAllAsync(orgIdGuid); - return new ListResponseModel(policies.Select(p => new PolicyResponseModel(p))); + return new ListResponseModel(policies.Select(p => new PolicyStatusResponseModel(p))); } [AllowAnonymous] diff --git a/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs index 8701a95bace4..0507de7a5575 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/PolicyResponseModel.cs @@ -4,7 +4,6 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.Models.Api; namespace Bit.Api.AdminConsole.Models.Response.Organizations; @@ -34,18 +33,6 @@ public PolicyResponseModel(Policy policy, string obj = "policy") RevisionDate = policy.RevisionDate; } - public PolicyResponseModel(PolicyStatus policyStatus, string obj = "policy") - : base(obj) - { - OrganizationId = policyStatus.OrganizationId; - Type = policyStatus.Type; - Enabled = policyStatus.Enabled; - if (!string.IsNullOrWhiteSpace(policyStatus.Data)) - { - Data = JsonSerializer.Deserialize>(policyStatus.Data); - } - } - public Guid Id { get; set; } public Guid OrganizationId { get; set; } public PolicyType Type { get; set; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs index d87f878c483d..66eaebdba8ab 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyQuery.cs @@ -16,10 +16,7 @@ public async Task> GetAllAsync(Guid organizationId) if (policies.All(p => p.Type != PolicyType.SendControls)) { var synthesized = await SynthesizeSendControlsStatusAsync(organizationId); - if (synthesized.Enabled) - { - results.Add(synthesized); - } + results.Add(synthesized); } return results; @@ -52,9 +49,8 @@ private async Task SynthesizeSendControlsStatusAsync(Guid organiza organizationId, PolicyType.SendOptions); var disableSend = disableSendPolicy?.Enabled ?? false; - var disableHideEmail = sendOptionsPolicy?.Enabled == true - && sendOptionsPolicy.GetDataModel().DisableHideEmail; - var enabled = disableSend || disableHideEmail; + var disableHideEmail = sendOptionsPolicy?.GetDataModel().DisableHideEmail ?? false; + var enabled = (disableSendPolicy?.Enabled ?? false) || (sendOptionsPolicy?.Enabled ?? false); var data = new SendControlsPolicyData { diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs index 2f6b1ada8052..c12b5702c747 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs @@ -13,7 +13,9 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls /// always stays current for when the flag is eventually enabled. /// -public class DisableSendSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +public class DisableSendSyncPolicyEvent( + IPolicyRepository policyRepository, + TimeProvider timeProvider) : IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.DisableSend; @@ -38,7 +40,7 @@ public async Task ExecutePostUpsertSideEffectAsync( var sendControlsPolicyData = sendControlsPolicy.GetDataModel(); sendControlsPolicyData.DisableSend = postUpsertedPolicyState.Enabled; - if (sendOptionsPolicy?.Enabled == true) + if (sendOptionsPolicy != null) { sendControlsPolicyData.DisableHideEmail = sendOptionsPolicy.GetDataModel().DisableHideEmail; @@ -49,6 +51,8 @@ public async Task ExecutePostUpsertSideEffectAsync( // Step 2: sync Enabled status. SendControlsPolicy is enabled if either legacy policy is enabled sendControlsPolicy.Enabled = postUpsertedPolicyState.Enabled || (sendOptionsPolicy?.Enabled ?? false); + sendControlsPolicy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs index ab186a42ad06..bf85929a9903 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs @@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; @@ -23,38 +22,28 @@ public async Task ExecutePostUpsertSideEffectAsync( Policy postUpsertedPolicyState, Policy? previousPolicyState) { - var policyUpdate = policyRequest.PolicyUpdate; - - var sendControlsPolicy = await policyRepository.GetByOrganizationIdTypeAsync( - policyUpdate.OrganizationId, PolicyType.SendControls) ?? new Policy - { - Id = CoreHelpers.GenerateComb(), - OrganizationId = policyUpdate.OrganizationId, - Type = PolicyType.SendControls, - }; - var sendControlsPolicyData = - sendControlsPolicy.GetDataModel(); + postUpsertedPolicyState.GetDataModel(); - await UpsertLegacyPolicyAsync( - policyRequest.PolicyUpdate.OrganizationId, + await UpsertLegacyPolicyAsync( + postUpsertedPolicyState.OrganizationId, PolicyType.DisableSend, enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableSend, policyData: null); var sendOptionsData = new SendOptionsPolicyData { DisableHideEmail = sendControlsPolicyData.DisableHideEmail }; await UpsertLegacyPolicyAsync( - policyRequest.PolicyUpdate.OrganizationId, + postUpsertedPolicyState.OrganizationId, PolicyType.SendOptions, enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableHideEmail, - policyData: CoreHelpers.ClassToJsonData(sendOptionsData)); + policyData: sendOptionsData); } - private async Task UpsertLegacyPolicyAsync( + private async Task UpsertLegacyPolicyAsync( Guid organizationId, PolicyType type, bool enabled, - string? policyData) + T? policyData) where T : IPolicyDataModel, new() { var existing = await policyRepository.GetByOrganizationIdTypeAsync(organizationId, type); @@ -66,7 +55,10 @@ private async Task UpsertLegacyPolicyAsync( } policy.Enabled = enabled; - policy.Data = policyData; + if (policyData != null) + { + policy.SetDataModel(policyData); + } policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; await policyRepository.UpsertAsync(policy); diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs index 6e3486c38444..f82aa540f407 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs @@ -13,7 +13,9 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// Runs regardless of the pm-31885-send-controls feature flag to ensure SendControls /// always stays current for when the flag is eventually enabled. /// -public class SendOptionsSyncPolicyEvent(IPolicyRepository policyRepository) : IOnPolicyPostUpdateEvent +public class SendOptionsSyncPolicyEvent( + IPolicyRepository policyRepository, + TimeProvider timeProvider) : IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.SendOptions; @@ -43,6 +45,8 @@ public async Task ExecutePostUpsertSideEffectAsync( sendControlsPolicy.Enabled = postUpsertedPolicyState.Enabled || sendControlsPolicyData.DisableSend; + sendControlsPolicy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; + await policyRepository.UpsertAsync(sendControlsPolicy); } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index 017f982b2a10..24e80a53d181 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -1,4 +1,5 @@ // FIXME: Update this file to be null safe and then delete the line below + #nullable disable using Bit.Core.AdminConsole.OrganizationFeatures.Policies; @@ -14,11 +15,9 @@ namespace Bit.Core.Tools.Services; public class SendValidationService : ISendValidationService { - private readonly IUserRepository _userRepository; private readonly IOrganizationRepository _organizationRepository; private readonly IUserService _userService; - private readonly IFeatureService _featureService; private readonly GlobalSettings _globalSettings; private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPricingClient _pricingClient; @@ -27,7 +26,6 @@ public SendValidationService( IUserRepository userRepository, IOrganizationRepository organizationRepository, IUserService userService, - IFeatureService featureService, IPolicyRequirementQuery policyRequirementQuery, GlobalSettings globalSettings, IPricingClient pricingClient) @@ -35,7 +33,6 @@ public SendValidationService( _userRepository = userRepository; _organizationRepository = organizationRepository; _userService = userService; - _featureService = featureService; _policyRequirementQuery = policyRequirementQuery; _globalSettings = globalSettings; _pricingClient = pricingClient; @@ -50,33 +47,16 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) return; } - #region Fetch Policy Requirements Async - var sendControlsTask = _policyRequirementQuery.GetAsync(userId.Value); + // Once data migration has run, query only SendControls + // var sendControlsTask = _policyRequirementQuery.GetAsync(userId.Value); var disableSendTask = _policyRequirementQuery.GetAsync(userId.Value); var sendOptionsTask = _policyRequirementQuery.GetAsync(userId.Value); - await Task.WhenAll(sendControlsTask, disableSendTask, sendOptionsTask); + await Task.WhenAll(disableSendTask, sendOptionsTask); - var sendControlsRequirement = sendControlsTask.Result; + // var sendControlsRequirement = sendControlsTask.Result; var disableSendRequirement = disableSendTask.Result; var sendOptionsRequirement = sendOptionsTask.Result; - #endregion - - if (_featureService.IsEnabled(FeatureFlagKeys.SendControls)) - { - if (sendControlsRequirement.DisableSend || disableSendRequirement.DisableSend) - { - throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send."); - } - - if ((sendControlsRequirement.DisableHideEmail || sendOptionsRequirement.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."); - } - - return; - } if (disableSendRequirement.DisableSend) { @@ -85,7 +65,8 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) if (sendOptionsRequirement.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."); + 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."); } } @@ -123,6 +104,7 @@ public async Task StorageRemainingForSendAsync(Send send) var premiumPlan = await _pricingClient.GetAvailablePremiumPlan(); provided = (short)premiumPlan.Storage.Provided; } + storageBytesRemaining = user.StorageBytesRemaining(provided); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs index e2bb203974bc..aea974a60d8f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs @@ -1,13 +1,10 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -41,7 +38,7 @@ await sutProvider.GetDependency() p.OrganizationId == policyUpdate.OrganizationId && p.Type == PolicyType.SendControls && p.Enabled == true && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true))); + (p.GetDataModel().DisableSend == true))); } [Theory, BitAutoData] @@ -70,7 +67,7 @@ await sutProvider.GetDependency() .UpsertAsync(Arg.Is(p => p.Id == existingSendControlsPolicy.Id && p.Enabled == false && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == false))); + (p.GetDataModel().DisableSend == false))); } [Theory, BitAutoData] @@ -101,7 +98,7 @@ await sutProvider.GetDependency() .Received(1) .UpsertAsync(Arg.Is(p => p.Enabled == true && // stays enabled because SendOptions is still enabled - CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); + p.GetDataModel().DisableHideEmail == true)); } [Theory, BitAutoData] @@ -131,7 +128,7 @@ await sutProvider.GetDependency() p.OrganizationId == policyUpdate.OrganizationId && p.Type == PolicyType.SendControls && p.Enabled == true && - CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableSend == true && - CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); + p.GetDataModel().DisableSend == true && + p.GetDataModel().DisableHideEmail == true)); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs index f483c967c59e..b10a2f28c1d4 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs @@ -1,13 +1,10 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -28,10 +25,10 @@ public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableSend_ToLegacyDisa postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = false }); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) - .Returns(postUpsertedPolicy); + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -55,10 +52,10 @@ public async Task ExecutePostUpsertSideEffectAsync_SyncsDisableHideEmail_ToLegac postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = false, DisableHideEmail = true }); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) - .Returns(postUpsertedPolicy); + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -70,7 +67,7 @@ await sutProvider.GetDependency() p.OrganizationId == policyUpdate.OrganizationId && p.Type == PolicyType.SendOptions && p.Enabled == true && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); + (p.GetDataModel().DisableHideEmail == true))); } [Theory, BitAutoData] @@ -83,10 +80,10 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesLegacyPolicies_WhenSe postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) - .Returns(postUpsertedPolicy); + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns((Policy?)null); sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, Arg.Is(t => t != PolicyType.SendControls)) + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns((Policy?)null); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -117,9 +114,6 @@ public async Task ExecutePostUpsertSideEffectAsync_UpdatesExistingLegacyPolicies existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true, DisableHideEmail = true }); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendControls) - .Returns(postUpsertedPolicy); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) .Returns(existingDisableSendPolicy); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs index e32a3c023436..7b71a2b09097 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs @@ -1,13 +1,10 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -41,7 +38,7 @@ await sutProvider.GetDependency() .Received(1) .UpsertAsync(Arg.Is(p => p.Enabled == false && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == false))); + (p.GetDataModel().DisableHideEmail == false))); } [Theory, BitAutoData] @@ -68,7 +65,7 @@ await sutProvider.GetDependency() .UpsertAsync(Arg.Is(p => p.Id == existingSendControlsPolicy.Id && p.Enabled == true && - (CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true))); + (p.GetDataModel().DisableHideEmail == true))); } [Theory, BitAutoData] @@ -93,6 +90,6 @@ await sutProvider.GetDependency() p.OrganizationId == policyUpdate.OrganizationId && p.Type == PolicyType.SendControls && p.Enabled == true && - CoreHelpers.LoadClassFromJsonData(p.Data)!.DisableHideEmail == true)); + p.GetDataModel().DisableHideEmail == true)); } } diff --git a/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs b/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs index 58d2404f76a3..cffb120a3254 100644 --- a/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs +++ b/test/Core.Test/OrganizationFeatures/Policies/PolicyQueryTests.cs @@ -141,7 +141,7 @@ public async Task GetAllAsync_WithoutSendControls_LegacyEnabled_SynthesizesSendC } [Theory, BitAutoData] - public async Task GetAllAsync_WithoutSendControls_NoLegacyEnabled_NoExtraEntry( + public async Task GetAllAsync_WithoutSendControls_NoLegacyEnabled_SynthesizesDisabledEntry( SutProvider sutProvider, Guid organizationId) { @@ -167,8 +167,115 @@ public async Task GetAllAsync_WithoutSendControls_NoLegacyEnabled_NoExtraEntry( // Act var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); - // Assert — only the original policy, no synthesized entry - Assert.Single(results); - Assert.Equal(PolicyType.SingleOrg, results[0].Type); + // Assert — original policy + disabled synthesized SendControls entry + Assert.Equal(2, results.Count); + Assert.Contains(results, p => p.Type == PolicyType.SingleOrg); + var synthesized = results.Single(p => p.Type == PolicyType.SendControls); + Assert.False(synthesized.Enabled); + } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithoutSendControls_DisabledSendOptionsWithConfig_PreservesDisableHideEmail( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange — SendOptions is disabled but has DisableHideEmail = true configured + var sendOptionsPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SendOptions, + Enabled = false, + }; + sendOptionsPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { sendOptionsPolicy }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend) + .ReturnsNull(); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.SendOptions) + .Returns(sendOptionsPolicy); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert — synthesized SendControls preserves DisableHideEmail but is not enabled + var synthesized = results.Single(p => p.Type == PolicyType.SendControls); + Assert.False(synthesized.Enabled); + var data = synthesized.GetDataModel(); + Assert.True(data.DisableHideEmail); + } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithoutSendControls_EnabledSendOptionsWithDisableHideEmail_SynthesizesEnabledEntry( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange — SendOptions is enabled AND DisableHideEmail is true + var sendOptionsPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SendOptions, + Enabled = true, + }; + sendOptionsPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { sendOptionsPolicy }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend) + .ReturnsNull(); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.SendOptions) + .Returns(sendOptionsPolicy); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert — Enabled derives from SendOptions.Enabled, DisableHideEmail from SendOptions data + var synthesized = results.Single(p => p.Type == PolicyType.SendControls); + Assert.True(synthesized.Enabled); + var data = synthesized.GetDataModel(); + Assert.True(data.DisableHideEmail); + } + + [Theory, BitAutoData] + public async Task GetAllAsync_WithoutSendControls_EnabledSendOptionsWithoutDisableHideEmail_StillEnabled( + SutProvider sutProvider, + Guid organizationId) + { + // Arrange — SendOptions is enabled but DisableHideEmail is false + var sendOptionsPolicy = new Policy + { + OrganizationId = organizationId, + Type = PolicyType.SendOptions, + Enabled = true, + }; + sendOptionsPolicy.SetDataModel(new SendOptionsPolicyData { DisableHideEmail = false }); + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organizationId) + .Returns(new List { sendOptionsPolicy }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.DisableSend) + .ReturnsNull(); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organizationId, PolicyType.SendOptions) + .Returns(sendOptionsPolicy); + + // Act + var results = (await sutProvider.Sut.GetAllAsync(organizationId)).ToList(); + + // Assert — Enabled is true because SendOptions is enabled, regardless of DisableHideEmail + var synthesized = results.Single(p => p.Type == PolicyType.SendControls); + Assert.True(synthesized.Enabled); + var data = synthesized.GetDataModel(); + Assert.False(data.DisableHideEmail); } } diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index fc6fd487a93c..cf721e9f6a2d 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -179,93 +179,4 @@ public async Task ValidateUserCanSaveAsync_WhenPoliciesDoNotApply_Success( // No exception implies success await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); } - - [Theory, BitAutoData] - public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenDisableSendApplies( - SutProvider sutProvider, Send send, Guid userId) - { - send.HideEmail = false; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendControlsPolicyRequirement { DisableSend = true, DisableHideEmail = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new DisableSendPolicyRequirement { DisableSend = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Contains("you are only able to delete an existing Send", exception.Message); - } - - [Theory, BitAutoData] - public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenLegacyDisableSendApplies( - SutProvider sutProvider, Send send, Guid userId) - { - send.HideEmail = false; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new DisableSendPolicyRequirement { DisableSend = true }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Contains("you are only able to delete an existing Send", exception.Message); - } - - [Theory, BitAutoData] - public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenDisableHideEmailApplies( - SutProvider sutProvider, Send send, Guid userId) - { - send.HideEmail = true; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = true }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new DisableSendPolicyRequirement { DisableSend = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Contains("you are not allowed to hide your email address", exception.Message); - } - - [Theory, BitAutoData] - public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_ThrowsWhenLegacySendOptionsDisableHideEmailApplies( - SutProvider sutProvider, Send send, Guid userId) - { - send.HideEmail = true; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new DisableSendPolicyRequirement { DisableSend = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = true }); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Contains("you are not allowed to hide your email address", exception.Message); - } - - [Theory, BitAutoData] - public async Task ValidateUserCanSaveAsync_WhenSendControlsFlagEnabled_NoPolicyRestrictions_Success( - SutProvider sutProvider, Send send, Guid userId) - { - send.HideEmail = true; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.SendControls).Returns(true); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new DisableSendPolicyRequirement { DisableSend = false }); - sutProvider.GetDependency().GetAsync(userId) - .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - - // No exception implies success - await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); - } } From d3cf561d84ff562ba6e99e6abc0406c340b65d64 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 31 Mar 2026 00:20:03 -0400 Subject: [PATCH 19/40] [PM-31884] Add Send control policy access control fields --- .../SendControlsAllowedAccessControl.cs | 8 +++++++ .../Policies/SendControlsPolicyData.cs | 5 ++++ .../SendControlsPolicyRequirement.cs | 12 ++++++++++ .../Services/SendValidationService.cs | 23 +++++++++++++++++-- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs new file mode 100644 index 000000000000..c4de273f9268 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +public enum SendWhoCanAccessType +{ + Any, + PasswordProtected, + SpecificPeople +} \ No newline at end of file diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs index 42d55aa40c4e..4aa09aa1ce48 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs @@ -8,4 +8,9 @@ public class SendControlsPolicyData : IPolicyDataModel public bool DisableSend { get; set; } [Display(Name = "DisableHideEmail")] public bool DisableHideEmail { get; set; } + [Display(Name = "AllowedAccessControl")] + public SendWhoCanAccessType? WhoCanAccess { get; set; } + [Display(Name = "AllowedDomains")] + [StringLength(1000)] + public string? AllowedDomains { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs index 229a39028c86..ea291b176af4 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs @@ -19,6 +19,16 @@ public class SendControlsPolicyRequirement : IPolicyRequirement /// Indicates whether the user is prohibited from hiding their email from the recipient of a Send. /// public bool DisableHideEmail { get; init; } + + /// + /// Indicates the access control that a user must specify on their Sends + /// + public SendWhoCanAccessType? WhoCanAccess { get; init; } + + /// + /// Indicates the domains the emails of an email-protected Send must use + /// + public string? AllowedDomains { get; init; } } public class SendControlsPolicyRequirementFactory : BasePolicyRequirementFactory @@ -35,6 +45,8 @@ public override SendControlsPolicyRequirement Create(IEnumerable { DisableSend = result.DisableSend || data.DisableSend, DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail, + WhoCanAccess = result.WhoCanAccess ?? data.WhoCanAccess, + AllowedDomains = result.AllowedDomains ?? data.AllowedDomains }); } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index 24e80a53d181..b99d6b2650b3 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -2,6 +2,7 @@ #nullable disable +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.Billing.Pricing; @@ -48,13 +49,13 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) } // Once data migration has run, query only SendControls - // var sendControlsTask = _policyRequirementQuery.GetAsync(userId.Value); + var sendControlsTask = _policyRequirementQuery.GetAsync(userId.Value); var disableSendTask = _policyRequirementQuery.GetAsync(userId.Value); var sendOptionsTask = _policyRequirementQuery.GetAsync(userId.Value); await Task.WhenAll(disableSendTask, sendOptionsTask); - // var sendControlsRequirement = sendControlsTask.Result; + var sendControlsRequirement = sendControlsTask.Result; var disableSendRequirement = disableSendTask.Result; var sendOptionsRequirement = sendOptionsTask.Result; @@ -68,6 +69,24 @@ public async Task ValidateUserCanSaveAsync(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 passwordRequired = sendControlsRequirement.WhoCanAccess == SendWhoCanAccessType.PasswordProtected; + var emailsRequired = sendControlsRequirement.WhoCanAccess == SendWhoCanAccessType.SpecificPeople; + if ((passwordRequired && send.Password == null) || (emailsRequired && send.Emails == null)) + { + var requiredAccessControl = passwordRequired ? "password" : emailsRequired ? "email verification" : "(cannot determine required auth)"; + throw new BadRequestException($"Due to an Enterpise Policy your Sends must be protected by {requiredAccessControl}"); + } + + if (emailsRequired && sendControlsRequirement.AllowedDomains != null) + { + var domains = sendControlsRequirement.AllowedDomains.Split(",").Select(domain => domain.Trim()); + var emails = send.Emails.Split(",").Select(email => email.Trim()); + if (emails.Any(email => !domains.Any(domain => email.EndsWith(domain)))) + { + throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): {string.Join(", ", domains)}"); + } + } } public async Task StorageRemainingForSendAsync(Send send) From 697bae265c185de50f47de62448cb00800753b36 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 7 Apr 2026 23:59:33 -0400 Subject: [PATCH 20/40] Disable and enable Sends based on policy compliance --- .../SendControlsSyncPolicyEvent.cs | 60 ++++++++++- .../Tools/Repositories/ISendRepository.cs | 7 ++ .../Tools/Repositories/SendRepository.cs | 9 ++ .../Tools/Repositories/SendRepository.cs | 15 +++ .../Send_SetDisabledByIds.sql | 14 +++ .../SendControlsSyncPolicyEventTests.cs | 100 ++++++++++++++++++ .../2026-04-07_00_SendSetDisabledByIds.sql | 21 ++++ 7 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql create mode 100644 util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs index bf85929a9903..2e4c8b7a4603 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs @@ -4,6 +4,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Repositories; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Repositories; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; @@ -13,7 +16,9 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; /// public class SendControlsSyncPolicyEvent( IPolicyRepository policyRepository, - TimeProvider timeProvider) : IOnPolicyPostUpdateEvent + TimeProvider timeProvider, + IOrganizationUserRepository organizationUserRepository, + ISendRepository sendRepository) : IOnPolicyPostUpdateEvent, IPolicyValidationEvent { public PolicyType Type => PolicyType.SendControls; @@ -37,6 +42,8 @@ await UpsertLegacyPolicyAsync( PolicyType.SendOptions, enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableHideEmail, policyData: sendOptionsData); + + await SetDisabledForSendsByPolicyAsync(postUpsertedPolicyState, sendControlsPolicyData); } private async Task UpsertLegacyPolicyAsync( @@ -63,4 +70,55 @@ private async Task UpsertLegacyPolicyAsync( await policyRepository.UpsertAsync(policy); } + + public Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + var dataModel = policyRequest.PolicyUpdate.GetDataModel(); + if (dataModel.AllowedDomains is not null && dataModel.WhoCanAccess != SendWhoCanAccessType.SpecificPeople) + { + return Task.FromResult("Allowed domains can only be set when the required access type is set to specific people"); + } + return Task.FromResult(string.Empty); + } + + public async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData) + { + var orgUsers = await organizationUserRepository.GetManyByOrganizationAsync(postUpsertedPolicyState.OrganizationId, null); + var orgUserIds = orgUsers.Where(w => w.UserId != null).Select(s => s.UserId!.Value).ToList(); + var domains = (sendControlsPolicyData.AllowedDomains ?? "").Split(",").Select(d => d.Trim()); + var enabled = new List(); + var disabled = new List(); + foreach (var userId in orgUserIds) + { + var userSends = await sendRepository.GetManyByUserIdAsync(userId); + foreach (var userSend in userSends) + { + Console.WriteLine(userSend); + // If the policy is no longer in effect then re-enable all Sends + if (!postUpsertedPolicyState.Enabled) + { + enabled.Add(userSend.Id); + } else if ( + sendControlsPolicyData.DisableSend || + (sendControlsPolicyData.DisableHideEmail && (userSend.HideEmail ?? false)) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && userSend.AuthType != AuthType.Password) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && userSend.AuthType != AuthType.Email) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && domains.Any() && (userSend.Emails ?? "").Split(",").Select(e => e.Trim()).Any(e => !domains.Any(d => e.EndsWith(d))))) + { + disabled.Add(userSend.Id); + } else + { + enabled.Add(userSend.Id); + } + } + } + if (enabled.Count > 0) { + await sendRepository.UpdateManyDisabledAsync(enabled, false); + } + if (disabled.Count > 0) + { + await sendRepository.UpdateManyDisabledAsync(disabled, true); + } + return; + } } diff --git a/src/Core/Tools/Repositories/ISendRepository.cs b/src/Core/Tools/Repositories/ISendRepository.cs index 6de89f03748e..159197815f0a 100644 --- a/src/Core/Tools/Repositories/ISendRepository.cs +++ b/src/Core/Tools/Repositories/ISendRepository.cs @@ -42,4 +42,11 @@ public interface ISendRepository : IRepository /// A list of sends with updated data UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, IEnumerable sends); + + /// + /// Updates the 'Disabled' field for Sends by IDs in bulk + /// + /// A list of Send IDs to update + /// The value to set the 'Disabled' field to + Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled); } diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index 144e08021d71..fa95703fb1af 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -145,6 +145,15 @@ INNER JOIN }; } + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) + { + using var connection = new SqlConnection(ConnectionString); + var results = await connection.ExecuteAsync( + $"[{Schema}].[Send_SetDisabledByIds]", + new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled }, + commandType: CommandType.StoredProcedure); + } + private async Task ProtectDataAndSaveAsync(Send send, Func saveTask) { if (send == null) diff --git a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs index adf3fcc1f1cd..59c4430cf5a1 100644 --- a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs @@ -94,4 +94,19 @@ public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, }; } + /// + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var sends = await GetDbSet(dbContext) + .Where(s => ids.Contains(s.Id)) + .ToListAsync(); + foreach (var send in sends) + { + send.Disabled = disabled; + } + + await dbContext.SaveChangesAsync(); + } } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql new file mode 100644 index 000000000000..c3039a23626a --- /dev/null +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql @@ -0,0 +1,14 @@ +CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] + @Ids AS [dbo].[GuidIdArray] READONLY, + @Disabled BIT +AS +BEGIN + SET NOCOUNT ON + + -- Set field + UPDATE [dbo].[Send] + SET + [Disabled] = @Disabled + WHERE + [Id] IN (SELECT * FROM @Ids) +END \ No newline at end of file diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs index b10a2f28c1d4..05a92c08cf80 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs @@ -4,7 +4,12 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; +using Bit.Core.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Core.Tools.Entities; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -135,4 +140,99 @@ await sutProvider.GetDependency() p.Id == existingSendOptionsPolicy.Id && p.Enabled == true)); } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableHideEmail = true, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "duckdodgers.com" }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + var orgUserId = new Guid(); + var orgUser = new OrganizationUser + { + UserId = orgUserId + }; + sutProvider.GetDependency() + .GetManyByOrganizationAsync(postUpsertedPolicy.OrganizationId, null) + .Returns([ orgUser ]); + var nonCompliantSend1 = new Send + { + Id = new Guid(), + HideEmail = true + }; + var nonCompliantSend2 = new Send + { + Id = new Guid(), + AuthType = AuthType.Email, + Emails = "marvin@mars.planet" + }; + sutProvider.GetDependency() + .GetManyByUserIdAsync(orgUserId) + .Returns([ nonCompliantSend1, nonCompliantSend2 ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_EnablesCompliantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableHideEmail = true, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "duckdodgers.com" }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + var orgUserId = new Guid(); + var orgUser = new OrganizationUser + { + UserId = orgUserId + }; + sutProvider.GetDependency() + .GetManyByOrganizationAsync(policyUpdate.OrganizationId, null) + .Returns([ orgUser ]); + + var compliantSend1 = new Send + { + AuthType = AuthType.Email, + Emails = "daffy@duckdodgers.com" + }; + sutProvider.GetDependency() + .GetManyByUserIdAsync(orgUserId) + .Returns([ compliantSend1 ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false); + } } diff --git a/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql b/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql new file mode 100644 index 000000000000..98d350e490b8 --- /dev/null +++ b/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID('[dbo].[Send_SetDisabledByIds]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Send_SetDisabledByIds] +END +GO + +CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] + @Ids AS [dbo].[GuidIdArray] READONLY, + @Disabled BIT +AS +BEGIN + SET NOCOUNT ON + + -- Set field + UPDATE [dbo].[Send] + SET + [Disabled] = @Disabled + WHERE + [Id] IN (SELECT * FROM @Ids) +END +GO \ No newline at end of file From 947c3b5ada3a3a55da97e311d996563d79d0afd9 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 8 Apr 2026 00:08:35 -0400 Subject: [PATCH 21/40] Remove stray merge change --- src/Core/AdminConsole/Enums/PolicyType.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Core/AdminConsole/Enums/PolicyType.cs b/src/Core/AdminConsole/Enums/PolicyType.cs index 17ce9c40dde2..455f59b3fa6f 100644 --- a/src/Core/AdminConsole/Enums/PolicyType.cs +++ b/src/Core/AdminConsole/Enums/PolicyType.cs @@ -63,10 +63,7 @@ public static string GetName(this PolicyType type) PolicyType.AutotypeDefaultSetting => "Autotype default setting", PolicyType.AutomaticUserConfirmation => "Automatically confirm invited users", PolicyType.BlockClaimedDomainAccountCreation => "Block account creation for claimed domains", -<<<<<<< HEAD -======= PolicyType.OrganizationUserNotification => "Vault banner message", ->>>>>>> main PolicyType.SendControls => "Send controls", }; } From 657ee7d0539afb08cf78583fa9cf655d5fd08710 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Thu, 9 Apr 2026 16:43:21 -0400 Subject: [PATCH 22/40] Address PR comments --- .../SendControlsSyncPolicyEvent.cs | 24 +++++- .../Tools/Repositories/ISendRepository.cs | 3 +- .../Services/SendValidationService.cs | 12 ++- .../Tools/Repositories/SendRepository.cs | 8 +- .../Tools/Repositories/SendRepository.cs | 16 ++-- .../Send_SetDisabledByIds.sql | 10 ++- .../SendControlsSyncPolicyEventTests.cs | 4 +- .../Services/SendValidationServiceTests.cs | 73 +++++++++++++++++++ .../2026-04-07_00_SendSetDisabledByIds.sql | 16 ++-- 9 files changed, 132 insertions(+), 34 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index f1f8bbf86c34..e0662b55963c 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -7,6 +7,7 @@ using Bit.Core.Repositories; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Repositories; +using Bit.Core.Tools.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; @@ -87,37 +88,52 @@ public async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicyStat var orgUserIds = orgUsers.Where(w => w.UserId != null).Select(s => s.UserId!.Value).ToList(); var domains = (sendControlsPolicyData.AllowedDomains ?? "").Split(",").Select(d => d.Trim()); var enabled = new List(); + var enabledSendUserIds = new List(); var disabled = new List(); + var disabledSendUserIds = new List(); foreach (var userId in orgUserIds) { var userSends = await sendRepository.GetManyByUserIdAsync(userId); + var userHadSendsEnabled = false; + var userHadSendsDisabled = false; foreach (var userSend in userSends) { - Console.WriteLine(userSend); // If the policy is no longer in effect then re-enable all Sends if (!postUpsertedPolicyState.Enabled) { enabled.Add(userSend.Id); + userHadSendsEnabled = true; + } else if ( sendControlsPolicyData.DisableSend || (sendControlsPolicyData.DisableHideEmail && (userSend.HideEmail ?? false)) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && userSend.AuthType != AuthType.Password) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && userSend.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && domains.Any() && (userSend.Emails ?? "").Split(",").Select(e => e.Trim()).Any(e => !domains.Any(d => e.EndsWith(d))))) + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && domains.Any() && (userSend.Emails ?? "").Split(",").Select(e => e.Trim()).Any(e => !domains.Any(d => SendValidationService.SendEmailMatchesDomain(e, d))))) { disabled.Add(userSend.Id); + userHadSendsDisabled = true; } else { enabled.Add(userSend.Id); + userHadSendsEnabled = true; } } + if (userHadSendsEnabled) + { + enabledSendUserIds.Add(userId); + } + if (userHadSendsDisabled) + { + disabledSendUserIds.Add(userId); + } } if (enabled.Count > 0) { - await sendRepository.UpdateManyDisabledAsync(enabled, false); + await sendRepository.UpdateManyDisabledAsync(enabled, false, enabledSendUserIds); } if (disabled.Count > 0) { - await sendRepository.UpdateManyDisabledAsync(disabled, true); + await sendRepository.UpdateManyDisabledAsync(disabled, true, disabledSendUserIds); } return; } diff --git a/src/Core/Tools/Repositories/ISendRepository.cs b/src/Core/Tools/Repositories/ISendRepository.cs index 159197815f0a..b9c46fe5c04b 100644 --- a/src/Core/Tools/Repositories/ISendRepository.cs +++ b/src/Core/Tools/Repositories/ISendRepository.cs @@ -48,5 +48,6 @@ UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, /// /// A list of Send IDs to update /// The value to set the 'Disabled' field to - Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled); + /// A list of UserIds to bump the account revision time for + Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds); } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index b99d6b2650b3..e60972d5bfc4 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -11,6 +11,7 @@ using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tools.Entities; +using Bit.Core.Utilities; namespace Bit.Core.Tools.Services; @@ -53,7 +54,7 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) var disableSendTask = _policyRequirementQuery.GetAsync(userId.Value); var sendOptionsTask = _policyRequirementQuery.GetAsync(userId.Value); - await Task.WhenAll(disableSendTask, sendOptionsTask); + await Task.WhenAll(sendControlsTask, disableSendTask, sendOptionsTask); var sendControlsRequirement = sendControlsTask.Result; var disableSendRequirement = disableSendTask.Result; @@ -82,13 +83,20 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) { var domains = sendControlsRequirement.AllowedDomains.Split(",").Select(domain => domain.Trim()); var emails = send.Emails.Split(",").Select(email => email.Trim()); - if (emails.Any(email => !domains.Any(domain => email.EndsWith(domain)))) + if (emails.Any(email => !domains.Any(domain => SendEmailMatchesDomain(email, domain)))) { throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): {string.Join(", ", domains)}"); } } } + public static bool SendEmailMatchesDomain(string email, string domain) + { + var emailDomain = EmailValidation.GetDomain(email); + return emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase) + || emailDomain.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); + } + public async Task StorageRemainingForSendAsync(Send send) { var storageBytesRemaining = 0L; diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index fa95703fb1af..63a561aebc95 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -145,13 +145,17 @@ INNER JOIN }; } - public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds) { using var connection = new SqlConnection(ConnectionString); - var results = await connection.ExecuteAsync( + await connection.ExecuteAsync( $"[{Schema}].[Send_SetDisabledByIds]", new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled }, commandType: CommandType.StoredProcedure); + await connection.ExecuteAsync( + $"[{Schema}].[User_BumpManyAccountRevisionDates]", + new { Ids = ids.ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); } private async Task ProtectDataAndSaveAsync(Send send, Func saveTask) diff --git a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs index 59c4430cf5a1..feb1793fb8ea 100644 --- a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs @@ -95,18 +95,16 @@ public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, } /// - public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - var sends = await GetDbSet(dbContext) - .Where(s => ids.Contains(s.Id)) - .ToListAsync(); - foreach (var send in sends) - { - send.Disabled = disabled; - } - + await dbContext.Sends.Where(s => ids.Contains(s.Id)) + .ExecuteUpdateAsync(setters => setters + .SetProperty(s => s.Disabled, disabled) + .SetProperty(s => s.RevisionDate, DateTime.UtcNow) + ); + await dbContext.UserBumpManyAccountRevisionDatesAsync([..userIds]); await dbContext.SaveChangesAsync(); } } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql index c3039a23626a..b8765485d2f4 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] +CREATE OR ALTER PROCEDURE [dbo].[Send_SetDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, @Disabled BIT AS @@ -6,9 +6,11 @@ BEGIN SET NOCOUNT ON -- Set field - UPDATE [dbo].[Send] + UPDATE + [dbo].[Send] SET - [Disabled] = @Disabled + [Disabled] = @Disabled, + [RevisionDate] = GETUTCDATE() WHERE - [Id] IN (SELECT * FROM @Ids) + [Id] IN (SELECT * FROM @Ids) END \ No newline at end of file diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index 37e4f4331272..f15e39aaab30 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -188,7 +188,7 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true, Arg.Is>(l => l.Count() == 1 && l.Contains(orgUserId))); } [Theory, BitAutoData] @@ -233,6 +233,6 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false, Arg.Is>(l => l.Count() == 1 && l.Contains(orgUserId))); } } diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index cf721e9f6a2d..da3c89a8b85e 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.Billing.Pricing; @@ -159,6 +160,9 @@ public async Task ValidateUserCanSaveAsync_WhenSendOptionsPolicyProhibitsHidingE sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = true }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { WhoCanAccess = SendWhoCanAccessType.Any }); // No exception implies success await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); @@ -175,8 +179,77 @@ public async Task ValidateUserCanSaveAsync_WhenPoliciesDoNotApply_Success( sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { WhoCanAccess = SendWhoCanAccessType.Any }); // No exception implies success await sutProvider.Sut.ValidateUserCanSaveAsync(userId, send); } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_WhenPasswordAuthRequiredByPolicy( + SutProvider sutProvider, Send send, Guid userId + ) + { + send.AuthType = AuthType.None; + send.Password = null; + send.Emails = null; + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.PasswordProtected }); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + Assert.Equal("Due to an Enterpise Policy your Sends must be protected by password", exception.Message); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_WhenEmailAuthRequiredByPolicy( + SutProvider sutProvider, Send send, Guid userId + ) + { + send.AuthType = AuthType.Password; + send.Password = "testpassword"; + send.Emails = null; + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.SpecificPeople }); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + Assert.Equal("Due to an Enterpise Policy your Sends must be protected by email verification", exception.Message); + } + + [Theory, BitAutoData] + public async Task ValidateUserCanSaveAsync_WhenEmailAuthAndDomainsRequiredByPolicy( + SutProvider sutProvider, Send send, Guid userId + ) + { + send.AuthType = AuthType.Email; + send.Password = null; + send.Emails = "badguy@fake-bitwarden.com"; + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new DisableSendPolicyRequirement { DisableSend = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); + + sutProvider.GetDependency().GetAsync(userId) + .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "bitwarden.com" }); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); + Assert.Equal("Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): bitwarden.com", exception.Message); + } } diff --git a/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql b/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql index 98d350e490b8..9b219eec1ea0 100644 --- a/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql +++ b/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql @@ -1,10 +1,4 @@ -IF OBJECT_ID('[dbo].[Send_SetDisabledByIds]') IS NOT NULL -BEGIN - DROP PROCEDURE [dbo].[Send_SetDisabledByIds] -END -GO - -CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] +CREATE OR ALTER PROCEDURE [dbo].[Send_SetDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, @Disabled BIT AS @@ -12,10 +6,12 @@ BEGIN SET NOCOUNT ON -- Set field - UPDATE [dbo].[Send] + UPDATE + [dbo].[Send] SET - [Disabled] = @Disabled + [Disabled] = @Disabled, + [RevisionDate] = GETUTCDATE() WHERE - [Id] IN (SELECT * FROM @Ids) + [Id] IN (SELECT * FROM @Ids) END GO \ No newline at end of file From 2bafe528bc683c9631bcf0547fa61c3a8048aecc Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Fri, 10 Apr 2026 11:47:24 -0400 Subject: [PATCH 23/40] More PR comment fixes --- .../PolicyEventHandlers/SendControlsSyncPolicyEvent.cs | 4 ++-- .../Tools/SendFeatures/Services/SendValidationService.cs | 2 +- .../Tools/Repositories/SendRepository.cs | 2 +- .../SendControlsSyncPolicyEventTests.cs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index e0662b55963c..acd6e6de6401 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -82,11 +82,11 @@ public Task ValidateAsync(SavePolicyModel policyRequest, Policy? current return Task.FromResult(string.Empty); } - public async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData) + private async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData) { var orgUsers = await organizationUserRepository.GetManyByOrganizationAsync(postUpsertedPolicyState.OrganizationId, null); var orgUserIds = orgUsers.Where(w => w.UserId != null).Select(s => s.UserId!.Value).ToList(); - var domains = (sendControlsPolicyData.AllowedDomains ?? "").Split(",").Select(d => d.Trim()); + var domains = (sendControlsPolicyData.AllowedDomains ?? "").Split(",").Select(d => d.Trim()).Where(d => d != ""); var enabled = new List(); var enabledSendUserIds = new List(); var disabled = new List(); diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index e60972d5bfc4..7d939cf80550 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -76,7 +76,7 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) if ((passwordRequired && send.Password == null) || (emailsRequired && send.Emails == null)) { var requiredAccessControl = passwordRequired ? "password" : emailsRequired ? "email verification" : "(cannot determine required auth)"; - throw new BadRequestException($"Due to an Enterpise Policy your Sends must be protected by {requiredAccessControl}"); + throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by {requiredAccessControl}"); } if (emailsRequired && sendControlsRequirement.AllowedDomains != null) diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index 63a561aebc95..fccceb246591 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -154,7 +154,7 @@ await connection.ExecuteAsync( commandType: CommandType.StoredProcedure); await connection.ExecuteAsync( $"[{Schema}].[User_BumpManyAccountRevisionDates]", - new { Ids = ids.ToGuidIdArrayTVP() }, + new { UserIds = userIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure); } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index f15e39aaab30..c217657aa8e9 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -160,7 +160,7 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns(existingSendOptionsPolicy); - var orgUserId = new Guid(); + var orgUserId = Guid.NewGuid(); var orgUser = new OrganizationUser { UserId = orgUserId @@ -170,12 +170,12 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( .Returns([ orgUser ]); var nonCompliantSend1 = new Send { - Id = new Guid(), + Id = Guid.NewGuid(), HideEmail = true }; var nonCompliantSend2 = new Send { - Id = new Guid(), + Id = Guid.NewGuid(), AuthType = AuthType.Email, Emails = "marvin@mars.planet" }; @@ -210,7 +210,7 @@ public async Task ExecutePostUpsertSideEffectAsync_EnablesCompliantSends( sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns(existingSendOptionsPolicy); - var orgUserId = new Guid(); + var orgUserId = Guid.NewGuid(); var orgUser = new OrganizationUser { UserId = orgUserId From c43c0793763bdb547c3c4908daa28ae9b84d682e Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Fri, 10 Apr 2026 14:19:38 -0400 Subject: [PATCH 24/40] Adjust email domain restriction logic, consolidate into a function --- .../SendControlsSyncPolicyEvent.cs | 4 +-- .../Services/SendValidationService.cs | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index acd6e6de6401..6f2aebe4eea0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -86,7 +86,6 @@ private async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicySta { var orgUsers = await organizationUserRepository.GetManyByOrganizationAsync(postUpsertedPolicyState.OrganizationId, null); var orgUserIds = orgUsers.Where(w => w.UserId != null).Select(s => s.UserId!.Value).ToList(); - var domains = (sendControlsPolicyData.AllowedDomains ?? "").Split(",").Select(d => d.Trim()).Where(d => d != ""); var enabled = new List(); var enabledSendUserIds = new List(); var disabled = new List(); @@ -109,7 +108,7 @@ private async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicySta (sendControlsPolicyData.DisableHideEmail && (userSend.HideEmail ?? false)) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && userSend.AuthType != AuthType.Password) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && userSend.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && domains.Any() && (userSend.Emails ?? "").Split(",").Select(e => e.Trim()).Any(e => !domains.Any(d => SendValidationService.SendEmailMatchesDomain(e, d))))) + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(userSend.Emails, sendControlsPolicyData.AllowedDomains))) { disabled.Add(userSend.Id); userHadSendsDisabled = true; @@ -135,6 +134,5 @@ private async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicySta { await sendRepository.UpdateManyDisabledAsync(disabled, true, disabledSendUserIds); } - return; } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index 7d939cf80550..0d67dc87122a 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -1,7 +1,5 @@ // FIXME: Update this file to be null safe and then delete the line below -#nullable disable - using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; @@ -81,20 +79,23 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) if (emailsRequired && sendControlsRequirement.AllowedDomains != null) { - var domains = sendControlsRequirement.AllowedDomains.Split(",").Select(domain => domain.Trim()); - var emails = send.Emails.Split(",").Select(email => email.Trim()); - if (emails.Any(email => !domains.Any(domain => SendEmailMatchesDomain(email, domain)))) + if (!SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsRequirement.AllowedDomains)) { - throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): {string.Join(", ", domains)}"); + throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): {sendControlsRequirement.AllowedDomains}"); } } } - public static bool SendEmailMatchesDomain(string email, string domain) + public static bool SendAllEmailsHaveAllowedDomains(string? emailsString, string? domainsString) { - var emailDomain = EmailValidation.GetDomain(email); - return emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase) - || emailDomain.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); + var domains = (domainsString ?? "").Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + var emails = (emailsString ?? "").Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + return emails.All(email => domains.Any(domain => + { + var emailDomain = EmailValidation.GetDomain(email); + return emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase) + || emailDomain.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); + })); } public async Task StorageRemainingForSendAsync(Send send) @@ -102,7 +103,7 @@ public async Task StorageRemainingForSendAsync(Send send) var storageBytesRemaining = 0L; if (send.UserId.HasValue) { - var user = await _userRepository.GetByIdAsync(send.UserId.Value); + var user = await _userRepository.GetByIdAsync(send.UserId.Value) ?? throw new NotFoundException("Send user not found"); if (!await _userService.CanAccessPremium(user)) { throw new BadRequestException("You must have premium status to use file Sends."); @@ -137,7 +138,7 @@ public async Task StorageRemainingForSendAsync(Send send) } else if (send.OrganizationId.HasValue) { - var org = await _organizationRepository.GetByIdAsync(send.OrganizationId.Value); + var org = await _organizationRepository.GetByIdAsync(send.OrganizationId.Value) ?? throw new NotFoundException("Send organization not found"); if (!org.MaxStorageGb.HasValue) { throw new BadRequestException("This organization cannot use file sends."); From c4e326cbe3860a1d656e51e2ac5b6d3da2ae41c0 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 14 Apr 2026 10:58:53 -0400 Subject: [PATCH 25/40] More PR comment fixes --- .../Tools/Repositories/SendRepository.cs | 4 ---- .../Tools/Stored Procedures/Send_SetDisabledByIds.sql | 11 +++++++++++ .../Tools/Services/SendValidationServiceTests.cs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index fccceb246591..e764e0237105 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -152,10 +152,6 @@ await connection.ExecuteAsync( $"[{Schema}].[Send_SetDisabledByIds]", new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled }, commandType: CommandType.StoredProcedure); - await connection.ExecuteAsync( - $"[{Schema}].[User_BumpManyAccountRevisionDates]", - new { UserIds = userIds.ToGuidIdArrayTVP() }, - commandType: CommandType.StoredProcedure); } private async Task ProtectDataAndSaveAsync(Send send, Func saveTask) diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql index b8765485d2f4..9a72b8e939c9 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql @@ -13,4 +13,15 @@ BEGIN [RevisionDate] = GETUTCDATE() WHERE [Id] IN (SELECT * FROM @Ids) + + -- Bump account revision dates + EXEC [dbo].[User_BumpManyAccountRevisionDates] + ( + SELECT DISTINCT + UserId + FROM + [dbo].[Send] + WHERE + [Id] IN (SELECT * FROM @Ids) + ) END \ No newline at end of file diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index da3c89a8b85e..df0e9065c4cd 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -206,7 +206,7 @@ public async Task ValidateUserCanSaveAsync_WhenPasswordAuthRequiredByPolicy( .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.PasswordProtected }); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Equal("Due to an Enterpise Policy your Sends must be protected by password", exception.Message); + Assert.Equal("Due to an Enterprise Policy your Sends must be protected by password", exception.Message); } [Theory, BitAutoData] @@ -228,7 +228,7 @@ public async Task ValidateUserCanSaveAsync_WhenEmailAuthRequiredByPolicy( .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.SpecificPeople }); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ValidateUserCanSaveAsync(userId, send)); - Assert.Equal("Due to an Enterpise Policy your Sends must be protected by email verification", exception.Message); + Assert.Equal("Due to an Enterprise Policy your Sends must be protected by email verification", exception.Message); } [Theory, BitAutoData] From f3a4f165f68d7607f85c338bfff5c60c165f0c1f Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 14 Apr 2026 13:59:29 -0400 Subject: [PATCH 26/40] Fix data migration and sproc files --- src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql | 2 +- ...DisabledByIds.sql => 2026-04-14_00_SendSetDisabledByIds.sql} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename util/Migrator/DbScripts/{2026-04-07_00_SendSetDisabledByIds.sql => 2026-04-14_00_SendSetDisabledByIds.sql} (100%) diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql index 9a72b8e939c9..828d35be1090 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql @@ -1,4 +1,4 @@ -CREATE OR ALTER PROCEDURE [dbo].[Send_SetDisabledByIds] +CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, @Disabled BIT AS diff --git a/util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql b/util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql similarity index 100% rename from util/Migrator/DbScripts/2026-04-07_00_SendSetDisabledByIds.sql rename to util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql From 3a5396529410dc74c2c641cf0b71f3904c15536f Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Sun, 19 Apr 2026 15:47:22 -0400 Subject: [PATCH 27/40] Even out database load by fetching all org Send IDs and processing in batches --- .../SendControlsSyncPolicyEvent.cs | 65 +++++++------------ .../Tools/Repositories/ISendRepository.cs | 16 ++++- .../Tools/Repositories/SendRepository.cs | 26 +++++++- .../Tools/Repositories/SendRepository.cs | 32 +++++++-- .../Stored Procedures/Send_ReadByIds.sql | 13 ++++ .../Stored Procedures/Send_ReadIdsByOrgId.sql | 24 +++++++ ...ByIds.sql => Send_UpdateDisabledByIds.sql} | 22 +++---- .../SendControlsSyncPolicyEventTests.cs | 4 +- .../2026-04-14_00_SendSetDisabledByIds.sql | 17 ----- ...-04-20_00_SendAccessControlPolicyProcs.sql | 58 +++++++++++++++++ 10 files changed, 193 insertions(+), 84 deletions(-) create mode 100644 src/Sql/dbo/Tools/Stored Procedures/Send_ReadByIds.sql create mode 100644 src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql rename src/Sql/dbo/Tools/Stored Procedures/{Send_SetDisabledByIds.sql => Send_UpdateDisabledByIds.sql} (53%) delete mode 100644 util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql create mode 100644 util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index 6f2aebe4eea0..fa8a89dd1ee5 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Repositories; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Repositories; using Bit.Core.Tools.Services; @@ -18,7 +17,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandler public class SendControlsSyncPolicyEvent( IPolicyRepository policyRepository, TimeProvider timeProvider, - IOrganizationUserRepository organizationUserRepository, ISendRepository sendRepository) : IOnPolicyPostUpdateEvent, IPolicyValidationEvent { public PolicyType Type => PolicyType.SendControls; @@ -44,7 +42,7 @@ await UpsertLegacyPolicyAsync( enabled: postUpsertedPolicyState.Enabled && sendControlsPolicyData.DisableHideEmail, policyData: sendOptionsData); - await SetDisabledForSendsByPolicyAsync(postUpsertedPolicyState, sendControlsPolicyData); + await UpdateSendsByPolicyAsync(postUpsertedPolicyState, sendControlsPolicyData); } private async Task UpsertLegacyPolicyAsync( @@ -82,57 +80,38 @@ public Task ValidateAsync(SavePolicyModel policyRequest, Policy? current return Task.FromResult(string.Empty); } - private async Task SetDisabledForSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData) + private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, SendControlsPolicyData sendControlsPolicyData) { - var orgUsers = await organizationUserRepository.GetManyByOrganizationAsync(postUpsertedPolicyState.OrganizationId, null); - var orgUserIds = orgUsers.Where(w => w.UserId != null).Select(s => s.UserId!.Value).ToList(); - var enabled = new List(); - var enabledSendUserIds = new List(); - var disabled = new List(); - var disabledSendUserIds = new List(); - foreach (var userId in orgUserIds) + var orgSendIds = await sendRepository.GetIdsByOrganizationIdAsync(postUpsertedPolicyState.OrganizationId); + foreach (var sendIdsChunk in orgSendIds.Chunk(50)) { - var userSends = await sendRepository.GetManyByUserIdAsync(userId); - var userHadSendsEnabled = false; - var userHadSendsDisabled = false; - foreach (var userSend in userSends) + var enabled = new List(); + var disabled = new List(); + var sendsChunk = await sendRepository.GetManyByIdsAsync(sendIdsChunk); + foreach (var send in sendsChunk) { - // If the policy is no longer in effect then re-enable all Sends - if (!postUpsertedPolicyState.Enabled) + if ( + // If the policy is disabled then we want to re-enable any Sends that were previously disabled + postUpsertedPolicyState.Enabled && + (sendControlsPolicyData.DisableSend || + (sendControlsPolicyData.DisableHideEmail && (send.HideEmail ?? false)) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && send.AuthType != AuthType.Password) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && send.AuthType != AuthType.Email) || + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains)))) { - enabled.Add(userSend.Id); - userHadSendsEnabled = true; - - } else if ( - sendControlsPolicyData.DisableSend || - (sendControlsPolicyData.DisableHideEmail && (userSend.HideEmail ?? false)) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && userSend.AuthType != AuthType.Password) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && userSend.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(userSend.Emails, sendControlsPolicyData.AllowedDomains))) - { - disabled.Add(userSend.Id); - userHadSendsDisabled = true; + disabled.Add(send.Id); } else { - enabled.Add(userSend.Id); - userHadSendsEnabled = true; + enabled.Add(send.Id); } } - if (userHadSendsEnabled) - { - enabledSendUserIds.Add(userId); + if (enabled.Count > 0) { + await sendRepository.UpdateManyDisabledAsync(enabled, false); } - if (userHadSendsDisabled) + if (disabled.Count > 0) { - disabledSendUserIds.Add(userId); + await sendRepository.UpdateManyDisabledAsync(disabled, true); } } - if (enabled.Count > 0) { - await sendRepository.UpdateManyDisabledAsync(enabled, false, enabledSendUserIds); - } - if (disabled.Count > 0) - { - await sendRepository.UpdateManyDisabledAsync(disabled, true, disabledSendUserIds); - } } } diff --git a/src/Core/Tools/Repositories/ISendRepository.cs b/src/Core/Tools/Repositories/ISendRepository.cs index b9c46fe5c04b..5b03a648c1fe 100644 --- a/src/Core/Tools/Repositories/ISendRepository.cs +++ b/src/Core/Tools/Repositories/ISendRepository.cs @@ -48,6 +48,18 @@ UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, /// /// A list of Send IDs to update /// The value to set the 'Disabled' field to - /// A list of UserIds to bump the account revision time for - Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds); + Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled); + + /// + /// Fetches the IDs of all ss of all Users that are members of an Organization + /// + /// The ID of the organization to fetch Sends for + Task> GetIdsByOrganizationIdAsync(Guid organizationId); + + /// + /// Load s in bulk by IDs + /// + /// The IDs of the ss to load + /// + Task> GetManyByIdsAsync(IEnumerable ids); } diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index e764e0237105..b98260378dd6 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -145,15 +145,37 @@ INNER JOIN }; } - public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds) + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) { using var connection = new SqlConnection(ConnectionString); await connection.ExecuteAsync( - $"[{Schema}].[Send_SetDisabledByIds]", + $"[{Schema}].[Send_UpdateDisabledByIds]", new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled }, commandType: CommandType.StoredProcedure); } + public async Task> GetIdsByOrganizationIdAsync(Guid organizationId) + { + using var connection = new SqlConnection(ConnectionString); + var sendIds = await connection.QueryAsync( + $"[{Schema}].[Send_ReadIdsByOrgId]", + new { Id = organizationId }, + commandType: CommandType.StoredProcedure); + return sendIds; + } + + public async Task> GetManyByIdsAsync(IEnumerable ids) + { + using var connection = new SqlConnection(ConnectionString); + var results = await connection.QueryAsync( + $"[{Schema}].[Send_ReadByIds]", + new { Ids = ids.ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); + var sends = results.ToList(); + UnprotectData(sends); + return sends; + } + private async Task ProtectDataAndSaveAsync(Send send, Func saveTask) { if (send == null) diff --git a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs index feb1793fb8ea..8d894c45e2b6 100644 --- a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs @@ -95,16 +95,34 @@ public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, } /// - public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled, IEnumerable userIds) + public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - await dbContext.Sends.Where(s => ids.Contains(s.Id)) - .ExecuteUpdateAsync(setters => setters - .SetProperty(s => s.Disabled, disabled) - .SetProperty(s => s.RevisionDate, DateTime.UtcNow) - ); - await dbContext.UserBumpManyAccountRevisionDatesAsync([..userIds]); + var sends = dbContext.Sends.Where(s => ids.Contains(s.Id)); + await sends.ExecuteUpdateAsync(setters => setters + .SetProperty(s => s.Disabled, disabled) + .SetProperty(s => s.RevisionDate, DateTime.UtcNow) + ); + var userIds = await sends.Select(s => s.User.Id).ToArrayAsync() ?? []; + await dbContext.UserBumpManyAccountRevisionDatesAsync(userIds); await dbContext.SaveChangesAsync(); } + + public async Task> GetIdsByOrganizationIdAsync(Guid organizationId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var orgUsers = await dbContext.OrganizationUsers.Where(ou => ou.OrganizationId == organizationId).ToListAsync(); + var orgUserSendIds = await dbContext.Sends.Where(s => orgUsers.Any(ou => ou.Id == s.UserId)).Select(s => s.Id).ToListAsync(); + return Mapper.Map>(orgUserSendIds); + } + + public async Task> GetManyByIdsAsync(IEnumerable ids) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.Sends.Where(s => ids.Contains(s.Id)).ToListAsync(); + return Mapper.Map>(results); + } } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadByIds.sql new file mode 100644 index 000000000000..e4716822bb6e --- /dev/null +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadByIds.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[Send_ReadByIds] + @Ids AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[SendView] + WHERE + [Id] IN (SELECT * FROM @Ids) +END \ No newline at end of file diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql new file mode 100644 index 000000000000..64db26a30826 --- /dev/null +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql @@ -0,0 +1,24 @@ +CREATE PROCEDURE [dbo].[Send_ReadIdsByOrgId] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + -- Get the IDs of all users in an org -- + DECLARE @OrgUserIds AS [GuidIdArray]; + INSERT INTO @OrgUserIds + SELECT DISTINCT + UserId + FROM + [dbo].[OrganizationUserView] + WHERE + OrganizationId = @Id + + -- Get the IDs of all Sends associated with those users -- + SELECT + Id + FROM + [dbo].[SendView] + WHERE + UserId IN (SELECT [Id] FROM @OrgUserIds) +END \ No newline at end of file diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql similarity index 53% rename from src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql rename to src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql index 828d35be1090..0b2edf6eba6e 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_SetDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[Send_SetDisabledByIds] +CREATE PROCEDURE [dbo].[Send_UpdateDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, @Disabled BIT AS @@ -7,21 +7,21 @@ BEGIN -- Set field UPDATE - [dbo].[Send] + [dbo].[Send] SET - [Disabled] = @Disabled, - [RevisionDate] = GETUTCDATE() + [Disabled] = @Disabled, + [RevisionDate] = GETUTCDATE() WHERE - [Id] IN (SELECT * FROM @Ids) + [Id] IN (SELECT * FROM @Ids) -- Bump account revision dates EXEC [dbo].[User_BumpManyAccountRevisionDates] ( - SELECT DISTINCT - UserId - FROM - [dbo].[Send] - WHERE - [Id] IN (SELECT * FROM @Ids) + SELECT DISTINCT + UserId + FROM + [dbo].[Send] + WHERE + [Id] IN (SELECT * FROM @Ids) ) END \ No newline at end of file diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index c217657aa8e9..f5a952968f76 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -188,7 +188,7 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true, Arg.Is>(l => l.Count() == 1 && l.Contains(orgUserId))); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true); } [Theory, BitAutoData] @@ -233,6 +233,6 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false, Arg.Is>(l => l.Count() == 1 && l.Contains(orgUserId))); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false); } } diff --git a/util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql b/util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql deleted file mode 100644 index 9b219eec1ea0..000000000000 --- a/util/Migrator/DbScripts/2026-04-14_00_SendSetDisabledByIds.sql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE OR ALTER PROCEDURE [dbo].[Send_SetDisabledByIds] - @Ids AS [dbo].[GuidIdArray] READONLY, - @Disabled BIT -AS -BEGIN - SET NOCOUNT ON - - -- Set field - UPDATE - [dbo].[Send] - SET - [Disabled] = @Disabled, - [RevisionDate] = GETUTCDATE() - WHERE - [Id] IN (SELECT * FROM @Ids) -END -GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql new file mode 100644 index 000000000000..bbeee923ec6a --- /dev/null +++ b/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql @@ -0,0 +1,58 @@ +CREATE OR ALTER PROCEDURE [dbo].[Send_UpdateDisabledByIds] + @Ids AS [dbo].[GuidIdArray] READONLY, + @Disabled BIT +AS +BEGIN + SET NOCOUNT ON + + -- Set field + UPDATE + [dbo].[Send] + SET + [Disabled] = @Disabled, + [RevisionDate] = GETUTCDATE() + WHERE + [Id] IN (SELECT * FROM @Ids) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Send_ReadByIds] + @Ids AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[SendView] + WHERE + [Id] IN (SELECT * FROM @Ids) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrgId] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + -- Get the IDs of all users in an org -- + DECLARE @OrgUserIds AS [GuidIdArray]; + INSERT INTO @OrgUserIds + SELECT DISTINCT + UserId + FROM + [dbo].[OrganizationUserView] + WHERE + OrganizationId = @Id + + -- Get the IDs of all Sends associated with those users -- + SELECT + Id + FROM + [dbo].[SendView] + WHERE + UserId IN (SELECT [Id] FROM @OrgUserIds) +END +GO \ No newline at end of file From 44dd3cda4a7241510b6c1f259d497e2b401ac6c9 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 22 Apr 2026 15:36:32 -0400 Subject: [PATCH 28/40] Fix tests and address review comments --- .../Tools/Repositories/SendRepository.cs | 2 +- .../Send_UpdateDisabledByIds.sql | 21 +- .../SendControlsSyncPolicyEventTests.cs | 270 ++++++++++++++++-- ...-04-20_00_SendAccessControlPolicyProcs.sql | 22 +- 4 files changed, 273 insertions(+), 42 deletions(-) diff --git a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs index d045d32238fa..133879c45ec5 100644 --- a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs @@ -152,7 +152,7 @@ public async Task> GetIdsByOrganizationIdAsync(Guid organizati using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); var orgUsers = await dbContext.OrganizationUsers.Where(ou => ou.OrganizationId == organizationId).ToListAsync(); - var orgUserSendIds = await dbContext.Sends.Where(s => orgUsers.Any(ou => ou.Id == s.UserId)).Select(s => s.Id).ToListAsync(); + var orgUserSendIds = await dbContext.Sends.Where(s => orgUsers.Any(ou => ou.UserId == s.UserId)).Select(s => s.Id).ToListAsync(); return Mapper.Map>(orgUserSendIds); } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql index 0b2edf6eba6e..2fcc840be46d 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql @@ -5,6 +5,8 @@ AS BEGIN SET NOCOUNT ON + DECLARE @UserIds [dbo].[GuidIdarray] + -- Set field UPDATE [dbo].[Send] @@ -13,15 +15,16 @@ BEGIN [RevisionDate] = GETUTCDATE() WHERE [Id] IN (SELECT * FROM @Ids) + + INSERT INTO @UserIds + SELECT DISTINCT + UserId + FROM + [dbo].[Send] + WHERE + [Id] IN (SELECT * FROM @Ids) + AND [UserId] IS NOT NULL -- Bump account revision dates - EXEC [dbo].[User_BumpManyAccountRevisionDates] - ( - SELECT DISTINCT - UserId - FROM - [dbo].[Send] - WHERE - [Id] IN (SELECT * FROM @Ids) - ) + EXEC [dbo].[User_BumpManyAccountRevisionDates] @UserIds END \ No newline at end of file diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index f5a952968f76..f77b0eb6bf2a 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -3,8 +3,6 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Entities; -using Bit.Core.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; @@ -142,7 +140,7 @@ await sutProvider.GetDependency() } [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( + public async Task ExecutePostUpsertSideEffectAsync_DisablingPolicyEnablesAllSends( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, @@ -152,7 +150,8 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableHideEmail = true, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "duckdodgers.com" }); + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { WhoCanAccess = SendWhoCanAccessType.PasswordProtected }); + postUpsertedPolicy.Enabled = false; sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) @@ -160,27 +159,23 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablesNonCompliantSends( sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns(existingSendOptionsPolicy); - var orgUserId = Guid.NewGuid(); - var orgUser = new OrganizationUser - { - UserId = orgUserId - }; - sutProvider.GetDependency() - .GetManyByOrganizationAsync(postUpsertedPolicy.OrganizationId, null) - .Returns([ orgUser ]); + var nonCompliantSend1 = new Send { Id = Guid.NewGuid(), - HideEmail = true + AuthType = AuthType.None, }; var nonCompliantSend2 = new Send { Id = Guid.NewGuid(), AuthType = AuthType.Email, - Emails = "marvin@mars.planet" }; + var sendIds = new List([ nonCompliantSend1.Id, nonCompliantSend2.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); sutProvider.GetDependency() - .GetManyByUserIdAsync(orgUserId) + .GetManyByIdsAsync(Arg.Any>()) .Returns([ nonCompliantSend1, nonCompliantSend2 ]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( @@ -188,11 +183,160 @@ await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2), true); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id)), false); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_DisableSendDisablesAllSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableSend = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + var otherwiseCompliantSend1 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.None, + }; + var otherwiseCompliantSend2 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Password, + }; + var sendIds = new List([ otherwiseCompliantSend1.Id, otherwiseCompliantSend2.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); + sutProvider.GetDependency() + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ otherwiseCompliantSend1, otherwiseCompliantSend2 ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2 && l.Contains(otherwiseCompliantSend1.Id) && l.Contains(otherwiseCompliantSend2.Id)), true); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_DisableHideEmailDisablesRelevantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableHideEmail = true }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + var compliantSend = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.None, + }; + var nonCompliantSend = new Send + { + Id = Guid.NewGuid(), + HideEmail = true + }; + var sendIds = new List([ compliantSend.Id, nonCompliantSend.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); + sutProvider.GetDependency() + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ compliantSend, nonCompliantSend ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(nonCompliantSend.Id)), true); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_AuthTypePasswordDisablesRelevantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { WhoCanAccess = SendWhoCanAccessType.PasswordProtected }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + var compliantSend = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Password, + }; + var nonCompliantSend1 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.None + }; + var nonCompliantSend2 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Email + }; + var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); + sutProvider.GetDependency() + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2 ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 2 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id)), true); } [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_EnablesCompliantSends( + public async Task ExecutePostUpsertSideEffectAsync_AuthTypeEmailNoDomainDisablesRelevantSends( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, @@ -202,7 +346,7 @@ public async Task ExecutePostUpsertSideEffectAsync_EnablesCompliantSends( postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { DisableHideEmail = true, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "duckdodgers.com" }); + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { WhoCanAccess = SendWhoCanAccessType.SpecificPeople }); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) @@ -210,29 +354,99 @@ public async Task ExecutePostUpsertSideEffectAsync_EnablesCompliantSends( sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) .Returns(existingSendOptionsPolicy); - var orgUserId = Guid.NewGuid(); - var orgUser = new OrganizationUser + + var compliantSend = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Email, + }; + var nonCompliantSend1 = new Send { - UserId = orgUserId + Id = Guid.NewGuid(), + AuthType = AuthType.None }; - sutProvider.GetDependency() - .GetManyByOrganizationAsync(policyUpdate.OrganizationId, null) - .Returns([ orgUser ]); + var nonCompliantSend2 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Password + }; + var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); + sutProvider.GetDependency() + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2 ]); - var compliantSend1 = new Send + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 2 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id)), true); + } + +[Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_AuthTypeEmailWithDomainDisablesRelevantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "duckdodgers.com" }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + + var compliantSend = new Send { + Id = Guid.NewGuid(), AuthType = AuthType.Email, Emails = "daffy@duckdodgers.com" }; + var nonCompliantSend1 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.None + }; + var nonCompliantSend2 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Password + }; + var nonCompliantSend3 = new Send + { + Id = Guid.NewGuid(), + AuthType = AuthType.Email, + Emails = "marvin@mars.planet" + }; + var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id, nonCompliantSend3.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); sutProvider.GetDependency() - .GetManyByUserIdAsync(orgUserId) - .Returns([ compliantSend1 ]); + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2, nonCompliantSend3 ]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); await sutProvider.GetDependency() .Received(1) - .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1), false); + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 3 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id) && l.Contains(nonCompliantSend3.Id)), true); } } diff --git a/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql index bbeee923ec6a..4ce0d14af149 100644 --- a/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql +++ b/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql @@ -5,14 +5,28 @@ AS BEGIN SET NOCOUNT ON + DECLARE @UserIds [dbo].[GuidIdarray] + -- Set field UPDATE - [dbo].[Send] + [dbo].[Send] SET - [Disabled] = @Disabled, - [RevisionDate] = GETUTCDATE() + [Disabled] = @Disabled, + [RevisionDate] = GETUTCDATE() + WHERE + [Id] IN (SELECT * FROM @Ids) + + INSERT INTO @UserIds + SELECT DISTINCT + UserId + FROM + [dbo].[Send] WHERE - [Id] IN (SELECT * FROM @Ids) + [Id] IN (SELECT * FROM @Ids) + AND [UserId] IS NOT NULL + + -- Bump account revision dates + EXEC [dbo].[User_BumpManyAccountRevisionDates] @UserIds END GO From 72a8b712b3a75a0864aa00336d3d150f97818c98 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Sun, 19 Apr 2026 22:57:06 -0400 Subject: [PATCH 29/40] [PM-32187] Add Send Type restriction to Send Controls policy --- .../Policies/SendControlsPolicyData.cs | 3 ++ .../SendControlsSyncPolicyEvent.cs | 3 +- .../SendControlsSyncPolicyEventTests.cs | 48 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs index 4aa09aa1ce48..102d7c07d8e7 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Tools.Enums; namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; @@ -13,4 +14,6 @@ public class SendControlsPolicyData : IPolicyDataModel [Display(Name = "AllowedDomains")] [StringLength(1000)] public string? AllowedDomains { get; set; } + [Display(Name = "RestrictSendType")] + public SendType? RestrictSendType { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index b33445713ca9..73d71b3128c8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -94,7 +94,8 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send (sendControlsPolicyData.DisableHideEmail && (send.HideEmail ?? false)) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && send.AuthType != AuthType.Password) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && send.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains)))) + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains))) || + (sendControlsPolicyData.RestrictSendType != null && send.Type != sendControlsPolicyData.RestrictSendType)) { disabled.Add(send.Id); } else diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index f77b0eb6bf2a..5085bf30be62 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -449,4 +449,52 @@ await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 3 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id) && l.Contains(nonCompliantSend3.Id)), true); } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_RestrictSendTypeDisablesNoncompliantSends( + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) + { + postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; + existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; + existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { RestrictSendType = SendType.Text }); + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) + .Returns(existingDisableSendPolicy); + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.SendOptions) + .Returns(existingSendOptionsPolicy); + var compliantSend = new Send + { + Id = Guid.NewGuid(), + Type = SendType.Text + }; + var nonCompliantSend = new Send + { + Id = Guid.NewGuid(), + Type = SendType.File + }; + var sendIds = new List([ compliantSend.Id, nonCompliantSend.Id ]); + sutProvider.GetDependency() + .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(sendIds); + sutProvider.GetDependency() + .GetManyByIdsAsync(Arg.Any>()) + .Returns([ compliantSend, nonCompliantSend ]); + + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( + new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); + + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1 && l.ElementAt(0) == compliantSend.Id), false); + await sutProvider.GetDependency() + .Received(1) + .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 1 && l.ElementAt(0) == nonCompliantSend.Id), true); + } } From 9e682196b67976eb838fb2c07ff935fcc7bb064e Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Thu, 23 Apr 2026 10:20:39 -0400 Subject: [PATCH 30/40] Fix Send Type check breaking reenabling behavior --- .../PolicyEventHandlers/SendControlsSyncPolicyEvent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index 73d71b3128c8..0cd3b6c25aee 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -94,8 +94,8 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send (sendControlsPolicyData.DisableHideEmail && (send.HideEmail ?? false)) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && send.AuthType != AuthType.Password) || (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && send.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains))) || - (sendControlsPolicyData.RestrictSendType != null && send.Type != sendControlsPolicyData.RestrictSendType)) + (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains)) || + (sendControlsPolicyData.RestrictSendType != null && send.Type != sendControlsPolicyData.RestrictSendType))) { disabled.Add(send.Id); } else From 183816bc342185b6e4ea866720d3ffdd8356ac01 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 4 May 2026 15:56:47 -0400 Subject: [PATCH 31/40] More PR comment fixes --- .../SendControlsSyncPolicyEvent.cs | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index b33445713ca9..9fb87f001395 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -4,6 +4,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Repositories; using Bit.Core.Tools.Services; @@ -89,12 +91,7 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send { if ( // If the policy is disabled then we want to re-enable any Sends that were previously disabled - postUpsertedPolicyState.Enabled && - (sendControlsPolicyData.DisableSend || - (sendControlsPolicyData.DisableHideEmail && (send.HideEmail ?? false)) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected && send.AuthType != AuthType.Password) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && send.AuthType != AuthType.Email) || - (sendControlsPolicyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, sendControlsPolicyData.AllowedDomains)))) + postUpsertedPolicyState.Enabled && SendIsNonCompliant(send, sendControlsPolicyData)) { disabled.Add(send.Id); } else @@ -111,4 +108,44 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send } } } + + private static bool SendIsNonCompliant(Send send, SendControlsPolicyData policyData) + { + if (policyData.DisableSend) + { + return true; + } + if (policyData.DisableHideEmail && (send.HideEmail ?? false)) + { + return true; + } + if (policyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected + && send.AuthType != AuthType.Password) + { + return true; + } + if (policyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople) + { + if (send.AuthType != AuthType.Email) + { + return true; + } + try + { + if (policyData.AllowedDomains != null && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, policyData.AllowedDomains)) + { + return true; + } + } + catch (BadRequestException) + { + // Send data not sent from our clients may not have validation guaranteeing their + // emails field contains valid email addresses. We can't verify such a Send against + // the allowed-domains list, so treat it as non-compliant and disable it rather than + // aborting the org-wide sweep. + return true; + } + } + return false; + } } From f567d52aca37959eaa9b0d3a772b043008821cbb Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 4 May 2026 16:07:46 -0400 Subject: [PATCH 32/40] Fix data migration script order --- ...cyProcs.sql => 2026-05-04_00_SendAccessControlPolicyProcs.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename util/Migrator/DbScripts/{2026-04-20_00_SendAccessControlPolicyProcs.sql => 2026-05-04_00_SendAccessControlPolicyProcs.sql} (100%) diff --git a/util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql similarity index 100% rename from util/Migrator/DbScripts/2026-04-20_00_SendAccessControlPolicyProcs.sql rename to util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql From 9cb87f2ff3f76f8c20e7364172d86eddaaa9bf2a Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 4 May 2026 16:19:48 -0400 Subject: [PATCH 33/40] Lint fixes --- .../SendControlsAllowedAccessControl.cs | 10 ++-- .../SendControlsSyncPolicyEvent.cs | 42 ++++++++-------- .../Services/SendValidationService.cs | 2 +- .../SendControlsSyncPolicyEventTests.cs | 50 +++++++++---------- .../Services/SendValidationServiceTests.cs | 10 ++-- 5 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs index c4de273f9268..9b12f95e3eec 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsAllowedAccessControl.cs @@ -1,8 +1,8 @@ -namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; public enum SendWhoCanAccessType { - Any, - PasswordProtected, - SpecificPeople -} \ No newline at end of file + Any, + PasswordProtected, + SpecificPeople +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index 9fb87f001395..b93a5f8318a3 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -94,12 +94,14 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send postUpsertedPolicyState.Enabled && SendIsNonCompliant(send, sendControlsPolicyData)) { disabled.Add(send.Id); - } else + } + else { enabled.Add(send.Id); } } - if (enabled.Count > 0) { + if (enabled.Count > 0) + { await sendRepository.UpdateManyDisabledAsync(enabled, false); } if (disabled.Count > 0) @@ -111,41 +113,41 @@ private async Task UpdateSendsByPolicyAsync(Policy postUpsertedPolicyState, Send private static bool SendIsNonCompliant(Send send, SendControlsPolicyData policyData) { - if (policyData.DisableSend) + if (policyData.DisableSend) { - return true; - } - if (policyData.DisableHideEmail && (send.HideEmail ?? false)) + return true; + } + if (policyData.DisableHideEmail && (send.HideEmail ?? false)) { - return true; - } + return true; + } if (policyData.WhoCanAccess == SendWhoCanAccessType.PasswordProtected - && send.AuthType != AuthType.Password) + && send.AuthType != AuthType.Password) { - return true; - } + return true; + } if (policyData.WhoCanAccess == SendWhoCanAccessType.SpecificPeople) - { + { if (send.AuthType != AuthType.Email) - { + { return true; } try { if (policyData.AllowedDomains != null && !SendValidationService.SendAllEmailsHaveAllowedDomains(send.Emails, policyData.AllowedDomains)) - { + { return true; - } - } + } + } catch (BadRequestException) - { + { // Send data not sent from our clients may not have validation guaranteeing their // emails field contains valid email addresses. We can't verify such a Send against // the allowed-domains list, so treat it as non-compliant and disable it rather than // aborting the org-wide sweep. - return true; + return true; } - } - return false; + } + return false; } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index 0d67dc87122a..1993ce893c96 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -96,7 +96,7 @@ public static bool SendAllEmailsHaveAllowedDomains(string? emailsString, string? return emailDomain.Equals(domain, StringComparison.OrdinalIgnoreCase) || emailDomain.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase); })); - } + } public async Task StorageRemainingForSendAsync(Send send) { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index f77b0eb6bf2a..66fe91683d22 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -2,12 +2,12 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Repositories; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -170,17 +170,17 @@ public async Task ExecutePostUpsertSideEffectAsync_DisablingPolicyEnablesAllSend Id = Guid.NewGuid(), AuthType = AuthType.Email, }; - var sendIds = new List([ nonCompliantSend1.Id, nonCompliantSend2.Id ]); + var sendIds = new List([nonCompliantSend1.Id, nonCompliantSend2.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ nonCompliantSend1, nonCompliantSend2 ]); + .Returns([nonCompliantSend1, nonCompliantSend2]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id)), false); @@ -216,17 +216,17 @@ public async Task ExecutePostUpsertSideEffectAsync_DisableSendDisablesAllSends( Id = Guid.NewGuid(), AuthType = AuthType.Password, }; - var sendIds = new List([ otherwiseCompliantSend1.Id, otherwiseCompliantSend2.Id ]); + var sendIds = new List([otherwiseCompliantSend1.Id, otherwiseCompliantSend2.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ otherwiseCompliantSend1, otherwiseCompliantSend2 ]); + .Returns([otherwiseCompliantSend1, otherwiseCompliantSend2]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count() == 2 && l.Contains(otherwiseCompliantSend1.Id) && l.Contains(otherwiseCompliantSend2.Id)), true); @@ -262,17 +262,17 @@ public async Task ExecutePostUpsertSideEffectAsync_DisableHideEmailDisablesRelev Id = Guid.NewGuid(), HideEmail = true }; - var sendIds = new List([ compliantSend.Id, nonCompliantSend.Id ]); + var sendIds = new List([compliantSend.Id, nonCompliantSend.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ compliantSend, nonCompliantSend ]); + .Returns([compliantSend, nonCompliantSend]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); @@ -316,17 +316,17 @@ public async Task ExecutePostUpsertSideEffectAsync_AuthTypePasswordDisablesRelev Id = Guid.NewGuid(), AuthType = AuthType.Email }; - var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id ]); + var sendIds = new List([compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2 ]); + .Returns([compliantSend, nonCompliantSend1, nonCompliantSend2]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); @@ -370,17 +370,17 @@ public async Task ExecutePostUpsertSideEffectAsync_AuthTypeEmailNoDomainDisables Id = Guid.NewGuid(), AuthType = AuthType.Password }; - var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id ]); + var sendIds = new List([compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2 ]); + .Returns([compliantSend, nonCompliantSend1, nonCompliantSend2]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); @@ -389,13 +389,13 @@ await sutProvider.GetDependency() .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 2 && l.Contains(nonCompliantSend1.Id) && l.Contains(nonCompliantSend2.Id)), true); } -[Theory, BitAutoData] + [Theory, BitAutoData] public async Task ExecutePostUpsertSideEffectAsync_AuthTypeEmailWithDomainDisablesRelevantSends( - [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, - [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, - [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, - SutProvider sutProvider) + [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, + [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, + [Policy(PolicyType.SendOptions, enabled: false)] Policy existingSendOptionsPolicy, + SutProvider sutProvider) { postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -431,17 +431,17 @@ public async Task ExecutePostUpsertSideEffectAsync_AuthTypeEmailWithDomainDisabl AuthType = AuthType.Email, Emails = "marvin@mars.planet" }; - var sendIds = new List([ compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id, nonCompliantSend3.Id ]); + var sendIds = new List([compliantSend.Id, nonCompliantSend1.Id, nonCompliantSend2.Id, nonCompliantSend3.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ compliantSend, nonCompliantSend1, nonCompliantSend2, nonCompliantSend3 ]); + .Returns([compliantSend, nonCompliantSend1, nonCompliantSend2, nonCompliantSend3]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); - + await sutProvider.GetDependency() .Received(1) .UpdateManyDisabledAsync(Arg.Is>(l => l.Count == 1 && l.Contains(compliantSend.Id)), false); diff --git a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs index df0e9065c4cd..4f5d627acbda 100644 --- a/test/Core.Test/Tools/Services/SendValidationServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendValidationServiceTests.cs @@ -160,7 +160,7 @@ public async Task ValidateUserCanSaveAsync_WhenSendOptionsPolicyProhibitsHidingE sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = true }); - + sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { WhoCanAccess = SendWhoCanAccessType.Any }); @@ -179,7 +179,7 @@ public async Task ValidateUserCanSaveAsync_WhenPoliciesDoNotApply_Success( sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - + sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { WhoCanAccess = SendWhoCanAccessType.Any }); @@ -201,7 +201,7 @@ public async Task ValidateUserCanSaveAsync_WhenPasswordAuthRequiredByPolicy( sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - + sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.PasswordProtected }); @@ -223,7 +223,7 @@ public async Task ValidateUserCanSaveAsync_WhenEmailAuthRequiredByPolicy( sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - + sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.SpecificPeople }); @@ -245,7 +245,7 @@ public async Task ValidateUserCanSaveAsync_WhenEmailAuthAndDomainsRequiredByPoli sutProvider.GetDependency().GetAsync(userId) .Returns(new SendOptionsPolicyRequirement { DisableHideEmail = false }); - + sutProvider.GetDependency().GetAsync(userId) .Returns(new SendControlsPolicyRequirement { DisableSend = false, DisableHideEmail = false, WhoCanAccess = SendWhoCanAccessType.SpecificPeople, AllowedDomains = "bitwarden.com" }); From f3b4404c2d142d5c3edda55ffba7ce84721f578f Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 5 May 2026 15:55:53 -0400 Subject: [PATCH 34/40] Address a few review comments --- .../Tools/Repositories/SendRepository.cs | 6 +++--- ...ReadIdsByOrgId.sql => Send_ReadIdsByOrganizationId.sql} | 6 +++--- .../Tools/Stored Procedures/Send_UpdateDisabledByIds.sql | 7 ++++--- .../2026-05-04_00_SendAccessControlPolicyProcs.sql | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) rename src/Sql/dbo/Tools/Stored Procedures/{Send_ReadIdsByOrgId.sql => Send_ReadIdsByOrganizationId.sql} (75%) diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index ea112bfefedb..2ce3600dc3ae 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -198,7 +198,7 @@ public async Task UpdateManyDisabledAsync(IEnumerable ids, bool disabled) using var connection = new SqlConnection(ConnectionString); await connection.ExecuteAsync( $"[{Schema}].[Send_UpdateDisabledByIds]", - new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled }, + new { Ids = ids.ToGuidIdArrayTVP(), Disabled = disabled, RevisionDate = DateTime.UtcNow }, commandType: CommandType.StoredProcedure); } @@ -206,8 +206,8 @@ public async Task> GetIdsByOrganizationIdAsync(Guid organizati { using var connection = new SqlConnection(ConnectionString); var sendIds = await connection.QueryAsync( - $"[{Schema}].[Send_ReadIdsByOrgId]", - new { Id = organizationId }, + $"[{Schema}].[Send_ReadIdsByOrganizationId]", + new { OrganizationId = organizationId }, commandType: CommandType.StoredProcedure); return sendIds; } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql similarity index 75% rename from src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql rename to src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql index 64db26a30826..a2ac2e040aa5 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrgId.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql @@ -1,5 +1,5 @@ -CREATE PROCEDURE [dbo].[Send_ReadIdsByOrgId] - @Id UNIQUEIDENTIFIER +CREATE PROCEDURE [dbo].[Send_ReadIdsByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER AS BEGIN SET NOCOUNT ON @@ -12,7 +12,7 @@ BEGIN FROM [dbo].[OrganizationUserView] WHERE - OrganizationId = @Id + OrganizationId = @OrganizationId -- Get the IDs of all Sends associated with those users -- SELECT diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql index 2fcc840be46d..1958dc2b4ff2 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql @@ -1,18 +1,19 @@ CREATE PROCEDURE [dbo].[Send_UpdateDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, - @Disabled BIT + @Disabled BIT, + @RevisionDate DATETIME2(7) AS BEGIN SET NOCOUNT ON - DECLARE @UserIds [dbo].[GuidIdarray] + DECLARE @UserIds [dbo].[GuidIdArray] -- Set field UPDATE [dbo].[Send] SET [Disabled] = @Disabled, - [RevisionDate] = GETUTCDATE() + [RevisionDate] = @RevisionDate WHERE [Id] IN (SELECT * FROM @Ids) diff --git a/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql index 4ce0d14af149..8ff4081aef5d 100644 --- a/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql +++ b/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql @@ -5,7 +5,7 @@ AS BEGIN SET NOCOUNT ON - DECLARE @UserIds [dbo].[GuidIdarray] + DECLARE @UserIds [dbo].[GuidIdArray] -- Set field UPDATE @@ -45,7 +45,7 @@ BEGIN END GO -CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrgId] +CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrganizationId] @Id UNIQUEIDENTIFIER AS BEGIN From 85e412c8d6ddabb02e3939dc462decde15434161 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 6 May 2026 08:12:07 -0400 Subject: [PATCH 35/40] Fix migration script --- .../2026-05-04_00_SendAccessControlPolicyProcs.sql | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql index 8ff4081aef5d..c3b91156d947 100644 --- a/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql +++ b/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql @@ -1,6 +1,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Send_UpdateDisabledByIds] @Ids AS [dbo].[GuidIdArray] READONLY, - @Disabled BIT + @Disabled BIT, + @RevisionDate DATETIME2(7) AS BEGIN SET NOCOUNT ON @@ -12,7 +13,7 @@ BEGIN [dbo].[Send] SET [Disabled] = @Disabled, - [RevisionDate] = GETUTCDATE() + [RevisionDate] = @RevisionDate WHERE [Id] IN (SELECT * FROM @Ids) @@ -46,7 +47,7 @@ END GO CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrganizationId] - @Id UNIQUEIDENTIFIER + @OrganizationId UNIQUEIDENTIFIER AS BEGIN SET NOCOUNT ON @@ -59,7 +60,7 @@ BEGIN FROM [dbo].[OrganizationUserView] WHERE - OrganizationId = @Id + OrganizationId = @OrganizationId -- Get the IDs of all Sends associated with those users -- SELECT From 1436fb28ddea55422d8a718dd1b75aa37ed6cfce Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 13 May 2026 17:04:57 -0400 Subject: [PATCH 36/40] Fix data protection merge bug --- .../Tools/Repositories/SendRepository.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index 6f205c139876..0e12d028fd5c 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -219,9 +219,7 @@ public async Task> GetManyByIdsAsync(IEnumerable ids) $"[{Schema}].[Send_ReadByIds]", new { Ids = ids.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure); - var sends = results.ToList(); - UnprotectData(sends); - return sends; + return results.Where(UnprotectData).ToList(); } private async Task ProtectDataAndSaveAsync(Send send, Func saveTask) From 3b628bd36040fd50688ca908ef1b4c2df9c70c35 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Fri, 15 May 2026 09:47:41 -0400 Subject: [PATCH 37/40] Update date of data migration file --- ...cyProcs.sql => 2026-05-15_00_SendAccessControlPolicyProcs.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename util/Migrator/DbScripts/{2026-05-04_00_SendAccessControlPolicyProcs.sql => 2026-05-15_00_SendAccessControlPolicyProcs.sql} (100%) diff --git a/util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql similarity index 100% rename from util/Migrator/DbScripts/2026-05-04_00_SendAccessControlPolicyProcs.sql rename to util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql From c358aaa02d61c75799dfa5f40d38e74c1c5347ec Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 22 Jun 2026 08:57:45 -0400 Subject: [PATCH 38/40] Fix merge conflicts, tests, update approach to storing Send type restrictions --- .../Organizations/Policies/SendControlsPolicyData.cs | 4 ++-- .../PolicyEventHandlers/SendControlsSyncPolicyEvent.cs | 2 +- .../PolicyRequirements/SendControlsPolicyRequirement.cs | 9 ++++++++- .../Tools/SendFeatures/Services/SendValidationService.cs | 6 ++++++ .../SendControlsSyncPolicyEventTests.cs | 4 ++-- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs index 102d7c07d8e7..840fcbe4e55e 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendControlsPolicyData.cs @@ -14,6 +14,6 @@ public class SendControlsPolicyData : IPolicyDataModel [Display(Name = "AllowedDomains")] [StringLength(1000)] public string? AllowedDomains { get; set; } - [Display(Name = "RestrictSendType")] - public SendType? RestrictSendType { get; set; } + [Display(Name = "AllowedSendTypes")] + public SendType[]? AllowedSendTypes { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index 6c1aa2298134..464ce2194058 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -148,7 +148,7 @@ private static bool SendIsNonCompliant(Send send, SendControlsPolicyData policyD return true; } } - if (policyData.RestrictSendType != null && send.Type != policyData.RestrictSendType) + if (policyData.AllowedSendTypes != null && !policyData.AllowedSendTypes.Contains(send.Type)) { return true; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs index ea291b176af4..88bfbcb34d25 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.Tools.Enums; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; @@ -29,6 +30,11 @@ public class SendControlsPolicyRequirement : IPolicyRequirement /// Indicates the domains the emails of an email-protected Send must use /// public string? AllowedDomains { get; init; } + + /// + /// Indicates the types of Send that can be created + /// + public SendType[]? AllowedSendTypes { get; init;} } public class SendControlsPolicyRequirementFactory : BasePolicyRequirementFactory @@ -46,7 +52,8 @@ public override SendControlsPolicyRequirement Create(IEnumerable DisableSend = result.DisableSend || data.DisableSend, DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail, WhoCanAccess = result.WhoCanAccess ?? data.WhoCanAccess, - AllowedDomains = result.AllowedDomains ?? data.AllowedDomains + AllowedDomains = result.AllowedDomains ?? data.AllowedDomains, + AllowedSendTypes = [.. (result.AllowedSendTypes ?? []).Concat(data.AllowedSendTypes ?? []).Distinct()] }); } } diff --git a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs index f78a377ff62c..4065944575be 100644 --- a/src/Core/Tools/SendFeatures/Services/SendValidationService.cs +++ b/src/Core/Tools/SendFeatures/Services/SendValidationService.cs @@ -9,6 +9,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; @@ -102,6 +103,11 @@ public async Task ValidateUserCanSaveAsync(Guid? userId, Send send) { throw new BadRequestException($"Due to an Enterprise Policy your Sends must be protected by email verification and access granted only to the following domain(s): {sendControlsRequirement.AllowedDomains}"); } + + if (sendControlsRequirement.AllowedSendTypes != null && !sendControlsRequirement.AllowedSendTypes.Contains(send.Type)) + { + throw new BadRequestException($"Due to an Enterprise policy your Sends must be of the following types: {string.Join(", ", sendControlsRequirement.AllowedSendTypes.Select(st => st == SendType.Text ? "Text" : st == SendType.File ? "File" : "Unknown"))}"); + } } public static bool SendAllEmailsHaveAllowedDomains(string? emailsString, string? domainsString) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index 8d64a9f1959d..8e59f01a9324 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -451,7 +451,7 @@ await sutProvider.GetDependency() } [Theory, BitAutoData] - public async Task ExecutePostUpsertSideEffectAsync_RestrictSendTypeDisablesNoncompliantSends( + public async Task ExecutePostUpsertSideEffectAsync_AllowedSendTypesDisablesNoncompliantSends( [PolicyUpdate(PolicyType.SendControls, enabled: true)] PolicyUpdate policyUpdate, [Policy(PolicyType.SendControls, enabled: true)] Policy postUpsertedPolicy, [Policy(PolicyType.DisableSend, enabled: false)] Policy existingDisableSendPolicy, @@ -461,7 +461,7 @@ public async Task ExecutePostUpsertSideEffectAsync_RestrictSendTypeDisablesNonco postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { RestrictSendType = SendType.Text }); + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { AllowedSendTypes = [ SendType.Text ] }); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) From f00128d48b3bbbd22fa1723f3898e05c520dfde3 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 22 Jun 2026 10:41:34 -0400 Subject: [PATCH 39/40] Address review comments, formatting issues, duplicate migration script --- .../SendControlsPolicyRequirement.cs | 4 +- .../Send_ReadIdsByOrganizationId.sql | 9 ++- .../Send_UpdateDisabledByIds.sql | 2 +- .../SendControlsSyncPolicyEventTests.cs | 6 +- ...-05-15_00_SendAccessControlPolicyProcs.sql | 73 ------------------- 5 files changed, 11 insertions(+), 83 deletions(-) delete mode 100644 util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs index 88bfbcb34d25..ba3337508992 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SendControlsPolicyRequirement.cs @@ -34,7 +34,7 @@ public class SendControlsPolicyRequirement : IPolicyRequirement /// /// Indicates the types of Send that can be created /// - public SendType[]? AllowedSendTypes { get; init;} + public SendType[]? AllowedSendTypes { get; init; } } public class SendControlsPolicyRequirementFactory : BasePolicyRequirementFactory @@ -53,7 +53,7 @@ public override SendControlsPolicyRequirement Create(IEnumerable DisableHideEmail = result.DisableHideEmail || data.DisableHideEmail, WhoCanAccess = result.WhoCanAccess ?? data.WhoCanAccess, AllowedDomains = result.AllowedDomains ?? data.AllowedDomains, - AllowedSendTypes = [.. (result.AllowedSendTypes ?? []).Concat(data.AllowedSendTypes ?? []).Distinct()] + AllowedSendTypes = result.AllowedSendTypes ?? data.AllowedSendTypes, }); } } diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql index a2ac2e040aa5..f9338434ce55 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_ReadIdsByOrganizationId.sql @@ -8,17 +8,18 @@ BEGIN DECLARE @OrgUserIds AS [GuidIdArray]; INSERT INTO @OrgUserIds SELECT DISTINCT - UserId + [UserId] FROM [dbo].[OrganizationUserView] WHERE - OrganizationId = @OrganizationId + [OrganizationId] = @OrganizationId + AND [UserId] IS NOT NULL -- Get the IDs of all Sends associated with those users -- SELECT - Id + [Id] FROM [dbo].[SendView] WHERE - UserId IN (SELECT [Id] FROM @OrgUserIds) + [UserId] IN (SELECT [Id] FROM @OrgUserIds) END \ No newline at end of file diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql index 1958dc2b4ff2..41f0009cf3ce 100644 --- a/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql +++ b/src/Sql/dbo/Tools/Stored Procedures/Send_UpdateDisabledByIds.sql @@ -19,7 +19,7 @@ BEGIN INSERT INTO @UserIds SELECT DISTINCT - UserId + [UserId] FROM [dbo].[Send] WHERE diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index 8e59f01a9324..4ae8c3774831 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -461,7 +461,7 @@ public async Task ExecutePostUpsertSideEffectAsync_AllowedSendTypesDisablesNonco postUpsertedPolicy.OrganizationId = policyUpdate.OrganizationId; existingDisableSendPolicy.OrganizationId = policyUpdate.OrganizationId; existingSendOptionsPolicy.OrganizationId = policyUpdate.OrganizationId; - postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { AllowedSendTypes = [ SendType.Text ] }); + postUpsertedPolicy.SetDataModel(new SendControlsPolicyData { AllowedSendTypes = [SendType.Text] }); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, PolicyType.DisableSend) @@ -479,13 +479,13 @@ public async Task ExecutePostUpsertSideEffectAsync_AllowedSendTypesDisablesNonco Id = Guid.NewGuid(), Type = SendType.File }; - var sendIds = new List([ compliantSend.Id, nonCompliantSend.Id ]); + var sendIds = new List([compliantSend.Id, nonCompliantSend.Id]); sutProvider.GetDependency() .GetIdsByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(sendIds); sutProvider.GetDependency() .GetManyByIdsAsync(Arg.Any>()) - .Returns([ compliantSend, nonCompliantSend ]); + .Returns([compliantSend, nonCompliantSend]); await sutProvider.Sut.ExecutePostUpsertSideEffectAsync( new SavePolicyModel(policyUpdate), postUpsertedPolicy, null); diff --git a/util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql b/util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql deleted file mode 100644 index c3b91156d947..000000000000 --- a/util/Migrator/DbScripts/2026-05-15_00_SendAccessControlPolicyProcs.sql +++ /dev/null @@ -1,73 +0,0 @@ -CREATE OR ALTER PROCEDURE [dbo].[Send_UpdateDisabledByIds] - @Ids AS [dbo].[GuidIdArray] READONLY, - @Disabled BIT, - @RevisionDate DATETIME2(7) -AS -BEGIN - SET NOCOUNT ON - - DECLARE @UserIds [dbo].[GuidIdArray] - - -- Set field - UPDATE - [dbo].[Send] - SET - [Disabled] = @Disabled, - [RevisionDate] = @RevisionDate - WHERE - [Id] IN (SELECT * FROM @Ids) - - INSERT INTO @UserIds - SELECT DISTINCT - UserId - FROM - [dbo].[Send] - WHERE - [Id] IN (SELECT * FROM @Ids) - AND [UserId] IS NOT NULL - - -- Bump account revision dates - EXEC [dbo].[User_BumpManyAccountRevisionDates] @UserIds -END -GO - -CREATE OR ALTER PROCEDURE [dbo].[Send_ReadByIds] - @Ids AS [dbo].[GuidIdArray] READONLY -AS -BEGIN - SET NOCOUNT ON - - SELECT - * - FROM - [dbo].[SendView] - WHERE - [Id] IN (SELECT * FROM @Ids) -END -GO - -CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrganizationId] - @OrganizationId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - -- Get the IDs of all users in an org -- - DECLARE @OrgUserIds AS [GuidIdArray]; - INSERT INTO @OrgUserIds - SELECT DISTINCT - UserId - FROM - [dbo].[OrganizationUserView] - WHERE - OrganizationId = @OrganizationId - - -- Get the IDs of all Sends associated with those users -- - SELECT - Id - FROM - [dbo].[SendView] - WHERE - UserId IN (SELECT [Id] FROM @OrgUserIds) -END -GO \ No newline at end of file From 9aa50d9e221c9625626242e9b40e23e6629428c5 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Mon, 22 Jun 2026 10:50:08 -0400 Subject: [PATCH 40/40] Add update script for Send_ReadIdsByOrganizationId proc --- ...2026-06-22_00_UpdateSendReadIdsByOrgId.sql | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 util/Migrator/DbScripts/2026-06-22_00_UpdateSendReadIdsByOrgId.sql diff --git a/util/Migrator/DbScripts/2026-06-22_00_UpdateSendReadIdsByOrgId.sql b/util/Migrator/DbScripts/2026-06-22_00_UpdateSendReadIdsByOrgId.sql new file mode 100644 index 000000000000..7225e80b4ab7 --- /dev/null +++ b/util/Migrator/DbScripts/2026-06-22_00_UpdateSendReadIdsByOrgId.sql @@ -0,0 +1,26 @@ +CREATE OR ALTER PROCEDURE [dbo].[Send_ReadIdsByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + -- Get the IDs of all users in an org -- + DECLARE @OrgUserIds AS [GuidIdArray]; + INSERT INTO @OrgUserIds + SELECT DISTINCT + [UserId] + FROM + [dbo].[OrganizationUserView] + WHERE + [OrganizationId] = @OrganizationId + AND [UserId] IS NOT NULL + + -- Get the IDs of all Sends associated with those users -- + SELECT + [Id] + FROM + [dbo].[SendView] + WHERE + [UserId] IN (SELECT [Id] FROM @OrgUserIds) +END +GO \ No newline at end of file