From 40d49f7084a8a599acb7f79c06c11033d7b1fc0f Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:22:44 -0400 Subject: [PATCH 01/27] [PM-41931] feat(dirt): Add ReportExclusions column to Cipher Per-user JSON map of cipher report exclusions, a sibling of Archives. Vault write procedures accept the new parameter but never write it, so saving a cipher cannot erase another user's exclusion. --- src/Core/Vault/Entities/Cipher.cs | 1 + .../Cipher/Cipher_Create.sql | 3 +- .../Cipher/Cipher_CreateWithCollections.sql | 1 + .../Cipher/Cipher_Update.sql | 3 +- .../Cipher/Cipher_UpdateWithCollections.sql | 1 + src/Sql/dbo/Vault/Tables/Cipher.sql | 1 + ...026-10-21_00_AddCipherReportExclusions.sql | 223 + ...1939_AddCipherReportExclusions.Designer.cs | 4629 ++++++++++++++++ ...0261021171939_AddCipherReportExclusions.cs | 28 + .../DatabaseContextModelSnapshot.cs | 3 + ...1844_AddCipherReportExclusions.Designer.cs | 4635 +++++++++++++++++ ...0261021171844_AddCipherReportExclusions.cs | 27 + .../DatabaseContextModelSnapshot.cs | 3 + ...1852_AddCipherReportExclusions.Designer.cs | 4618 ++++++++++++++++ ...0261021171852_AddCipherReportExclusions.cs | 27 + .../DatabaseContextModelSnapshot.cs | 3 + 16 files changed, 14204 insertions(+), 2 deletions(-) create mode 100644 util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql create mode 100644 util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.cs create mode 100644 util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.cs create mode 100644 util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.cs diff --git a/src/Core/Vault/Entities/Cipher.cs b/src/Core/Vault/Entities/Cipher.cs index 08562e5dfdec..fdc68ede867e 100644 --- a/src/Core/Vault/Entities/Cipher.cs +++ b/src/Core/Vault/Entities/Cipher.cs @@ -27,6 +27,7 @@ public class Cipher : ITableObject, ICloneable public Enums.CipherRepromptType? Reprompt { get; set; } public string Key { get; set; } public string Archives { get; set; } + public string ReportExclusions { get; set; } public void SetNewId() { diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql index 68e7b11c0710..ce3c00b3a9de 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql @@ -12,7 +12,8 @@ @DeletedDate DATETIME2(7), @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, - @Archives NVARCHAR(MAX) = NULL + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql index 071bf82f87cd..6b465649b2f1 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql @@ -13,6 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql index e92a8147eac1..b4764b11dd9d 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql @@ -12,7 +12,8 @@ @DeletedDate DATETIME2(7), @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, - @Archives NVARCHAR(MAX) = NULL + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql index c33d49e92ec2..972159674393 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql @@ -13,6 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Tables/Cipher.sql b/src/Sql/dbo/Vault/Tables/Cipher.sql index 75ebc1d61803..f02257705438 100644 --- a/src/Sql/dbo/Vault/Tables/Cipher.sql +++ b/src/Sql/dbo/Vault/Tables/Cipher.sql @@ -15,6 +15,7 @@ CREATE TABLE [dbo].[Cipher] ( [Key] VARCHAR(MAX) NULL, [ArchivedDate] DATETIME2 (7) NULL, [Archives] NVARCHAR(MAX) NULL, + [ReportExclusions] NVARCHAR(MAX) NULL, CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql new file mode 100644 index 000000000000..1fbfebeb7094 --- /dev/null +++ b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql @@ -0,0 +1,223 @@ +IF COL_LENGTH('[dbo].[Cipher]', 'ReportExclusions') IS NULL +BEGIN + ALTER TABLE [dbo].[Cipher] + ADD [ReportExclusions] NVARCHAR(MAX) NULL; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), -- not used + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL -- not used +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Cipher] + ( + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Favorites], + [Folders], + [CreationDate], + [RevisionDate], + [DeletedDate], + [Reprompt], + [Key], + [Archives] + ) + VALUES + ( + @Id, + CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + @OrganizationId, + @Type, + @Data, + @Favorites, + @Folders, + @CreationDate, + @RevisionDate, + @DeletedDate, + @Reprompt, + @Key, + @Archives + ) + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL -- not used +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Cipher] + SET + [UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + [OrganizationId] = @OrganizationId, + [Type] = @Type, + [Data] = @Data, + [Favorites] = @Favorites, + [Folders] = @Folders, + [Attachments] = @Attachments, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [DeletedDate] = @DeletedDate, + [Reprompt] = @Reprompt, + [Key] = @Key, + [Archives] = @Archives + WHERE + [Id] = @Id + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Cipher_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt, @Key, @Archives + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds + + -- Bump the account revision date AFTER collections are assigned. + IF @UpdateCollectionsSuccess = 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @Archives NVARCHAR(MAX) = NULL, + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + BEGIN TRANSACTION Cipher_UpdateWithCollections + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds + + IF @UpdateCollectionsSuccess < 0 + BEGIN + COMMIT TRANSACTION Cipher_UpdateWithCollections + SELECT -1 -- -1 = Failure + RETURN + END + + UPDATE + [dbo].[Cipher] + SET + [UserId] = NULL, + [OrganizationId] = @OrganizationId, + [Data] = @Data, + [Attachments] = @Attachments, + [RevisionDate] = @RevisionDate, + [DeletedDate] = @DeletedDate, + [Key] = @Key, + [Folders] = @Folders, + [Favorites] = @Favorites, + [Reprompt] = @Reprompt, + [Archives] = @Archives + -- No need to update CreationDate or Type since that data will not change + WHERE + [Id] = @Id + + COMMIT TRANSACTION Cipher_UpdateWithCollections + + IF @Attachments IS NOT NULL + BEGIN + EXEC [dbo].[Organization_UpdateStorage] @OrganizationId + EXEC [dbo].[User_UpdateStorage] @UserId + END + + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + + SELECT 0 -- 0 = Success +END +GO + +EXECUTE sp_refreshview N'[dbo].[CipherView]' +GO diff --git a/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs b/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs new file mode 100644 index 000000000000..54c8f45ffc57 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs @@ -0,0 +1,4629 @@ +// +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("20261021171939_AddCipherReportExclusions")] + partial class AddCipherReportExclusions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .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.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessRuleId") + .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("AccessRuleId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.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.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("ExemptFromBillingAutomation") + .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("MaxAutoscalePamSeats") + .HasColumnType("int"); + + 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("OrganizationKeyId") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("PamSeatMinimum") + .HasColumnType("int"); + + b.Property("PamSeats") + .HasColumnType("int"); + + 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("UseInviteLinks") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseMyItems") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePam") + .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("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationInviteLink", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowedDomains") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Invite") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsConfirmation") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInviteLink", (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") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + 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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.OrganizationPlanMigrationCohort", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ChurnDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MigrationPathId") + .HasColumnType("tinyint unsigned"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("ProactiveDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Name") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohort", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohortAssignment", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ChurnDiscountAppliedDate") + .HasColumnType("datetime(6)"); + + b.Property("CohortId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("MigratedDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RenewalNotificationSentDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ScheduledDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "CreationDate", "Id") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "ScheduledDate", "MigratedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohortAssignment", (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.OrganizationDeleteTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CompletedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("FailureCount") + .HasColumnType("int"); + + b.Property("ItemsDeletedCount") + .HasColumnType("bigint"); + + b.Property("LastError") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("StartDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskType") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CompletedDate", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationDeleteTask", (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("ReportFile") + .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.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.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("ClientVersion") + .HasMaxLength(43) + .HasColumnType("varchar(43)"); + + 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("LastActivityDate") + .HasColumnType("datetime(6)"); + + 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("SendId") + .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("OrganizationId") + .HasDatabaseName("IX_Event_OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "SendId", "Date") + .HasDatabaseName("IX_Event_OrganizationIdSendIdDate") + .HasAnnotation("SqlServer:Clustered", false); + + 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("AccessPam") + .HasColumnType("tinyint(1)"); + + 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("RevocationReason") + .HasColumnType("tinyint unsigned"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StatusNew") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("V2UpgradeToken") + .HasColumnType("longtext"); + + 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("ProviderId") + .HasColumnType("char(36)"); + + 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("ProviderId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganizationOrProvider", "((CASE WHEN \"UserId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"OrganizationId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"ProviderId\" IS NOT NULL THEN 1 ELSE 0 END)) = 1"); + }); + }); + + 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") + .IsRequired() + .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") + .IsRequired() + .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("LastApiKeyRotationDate") + .HasColumnType("datetime(6)"); + + 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("MasterPasswordSalt") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + 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("UserKeyId") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("V2UpgradeToken") + .HasColumnType("longtext"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.Pam.Models.AccessAuditEvent", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessConnectorId") + .HasColumnType("char(36)"); + + b.Property("AccessConnectorName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AccessLeaseId") + .HasColumnType("char(36)"); + + b.Property("AccessRequestId") + .HasColumnType("char(36)"); + + b.Property("AccessRuleId") + .HasColumnType("char(36)"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ActorId") + .HasColumnType("char(36)"); + + b.Property("ActorName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CorrelationId") + .HasColumnType("char(36)"); + + b.Property("Detail") + .HasColumnType("longtext"); + + b.Property("Kind") + .HasColumnType("tinyint unsigned"); + + b.Property("LeaseNotAfter") + .HasColumnType("datetime(6)"); + + b.Property("LeaseNotBefore") + .HasColumnType("datetime(6)"); + + b.Property("OccurredDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Phase") + .HasColumnType("tinyint unsigned"); + + b.Property("RequesterEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RequesterId") + .HasColumnType("char(36)"); + + b.Property("RequesterName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RotationConfigId") + .HasColumnType("char(36)"); + + b.Property("RotationJobId") + .HasColumnType("char(36)"); + + b.Property("RotationSource") + .HasColumnType("tinyint unsigned"); + + b.Property("RuleName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("SyncState") + .HasColumnType("tinyint unsigned"); + + b.Property("TargetSystemId") + .HasColumnType("char(36)"); + + b.Property("TargetSystemName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("OrganizationId", "OccurredDate", "Id") + .IsDescending(false, true, true); + + b.ToTable("AccessAuditEvent", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessDecision", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessRequestId") + .HasColumnType("char(36)"); + + b.Property("ApproverId") + .HasColumnType("char(36)"); + + b.Property("Comment") + .HasColumnType("longtext"); + + b.Property("ConditionKind") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeciderKind") + .HasColumnType("tinyint unsigned"); + + b.Property("EvaluationContext") + .HasColumnType("longtext"); + + b.Property("Verdict") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId"); + + b.ToTable("AccessDecision", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessRequestId") + .HasColumnType("char(36)"); + + b.Property("Action") + .HasColumnType("tinyint unsigned"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("NotAfter") + .HasColumnType("datetime(6)"); + + b.Property("NotBefore") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RequesterId") + .HasColumnType("char(36)"); + + b.Property("RevokedBy") + .HasColumnType("char(36)"); + + b.Property("RevokedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.HasIndex("CollectionId", "Action"); + + b.HasIndex("NotAfter", "Action"); + + b.HasIndex("CipherId", "Action", "NotAfter") + .IsDescending(false, false, true); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessLease", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Action") + .HasColumnType("tinyint unsigned"); + + b.Property("ActionDate") + .HasColumnType("datetime(6)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExtensionOfLeaseId") + .HasColumnType("char(36)"); + + b.Property("NotAfter") + .HasColumnType("datetime(6)"); + + b.Property("NotBefore") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reason") + .HasColumnType("longtext"); + + b.Property("RequesterId") + .HasColumnType("char(36)"); + + b.Property("RuleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ExtensionOfLeaseId"); + + b.HasIndex("RuleId"); + + b.HasIndex("CollectionId", "CreationDate"); + + b.HasIndex("OrganizationId", "Action"); + + b.HasIndex("RequesterId", "CreationDate"); + + b.HasIndex("CollectionId", "Action", "NotAfter"); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowsExtensions") + .HasColumnType("tinyint(1)"); + + b.Property("Conditions") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultLeaseDurationSeconds") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastEditedBy") + .HasColumnType("char(36)"); + + b.Property("MaxExtensionDurationSeconds") + .HasColumnType("int"); + + b.Property("MaxLeaseDurationSeconds") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SingleActiveLease") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("AccessRule", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKeyId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastHeartbeatDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamAccessConnector", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessConnectorId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("TargetSystemId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.HasIndex("AccessConnectorId", "TargetSystemId") + .IsUnique(); + + b.ToTable("PamAccessConnectorTargetAssignment", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.Property("AccessLeaseId") + .HasColumnType("char(36)"); + + b.Property("SweptDate") + .HasColumnType("datetime(6)"); + + b.HasKey("AccessLeaseId"); + + b.ToTable("PamLeaseExpirySweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Action") + .HasColumnType("tinyint unsigned"); + + b.Property("CipherUpdated") + .HasColumnType("tinyint(1)"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("FailureReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("JobId") + .HasColumnType("char(36)"); + + b.Property("ResolvedDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionTermination") + .HasColumnType("tinyint unsigned"); + + b.Property("SyncState") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedByAccessConnectorId", "CreationDate") + .IsDescending(false, true); + + b.HasIndex("JobId", "Action"); + + b.ToTable("PamRotationAttempt", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("RotateOnAccessEnd") + .HasColumnType("tinyint(1)"); + + b.Property("ScheduleCron") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SealedData") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("varchar(10000)"); + + b.Property("TargetSystemId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("CipherId") + .IsUnique(); + + b.HasIndex("NextRotationDate"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.ToTable("PamRotationConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Action") + .HasColumnType("tinyint unsigned"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("char(36)"); + + b.Property("ClaimedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("NextClaimableDate") + .HasColumnType("datetime(6)"); + + b.Property("RotationConfigId") + .HasColumnType("char(36)"); + + b.Property("Source") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("Action", "ExpirationDate"); + + b.HasIndex("ClaimedByAccessConnectorId", "Action"); + + b.HasIndex("RotationConfigId", "Action"); + + b.ToTable("PamRotationJob", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.Property("RotationJobId") + .HasColumnType("char(36)"); + + b.Property("SweptDate") + .HasColumnType("datetime(6)"); + + b.HasKey("RotationJobId"); + + b.ToTable("PamRotationJobTimeoutSweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Method") + .HasColumnType("tinyint unsigned"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SealedData") + .HasMaxLength(10000) + .HasColumnType("varchar(10000)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamTargetSystem", (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("ReportExclusions") + .HasColumnType("longtext"); + + 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.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("AccessRuleId") + .OnDelete(DeleteBehavior.Restrict); + + 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.AdminConsole.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationInviteLink", 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.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.OrganizationPlanMigrationCohortAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohort", "Cohort") + .WithMany() + .HasForeignKey("CohortId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cohort"); + + 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.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + 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.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + 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.Pam.Models.AccessAuditEvent", 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.Pam.Models.AccessDecision", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("ExtensionOfLeaseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("RuleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", 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.Pam.Models.PamAccessConnector", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", null) + .WithMany() + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", null) + .WithMany() + .HasForeignKey("AccessConnectorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("AccessLeaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", null) + .WithMany() + .HasForeignKey("RotationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("RotationJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", 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.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.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + 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.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/20261021171939_AddCipherReportExclusions.cs b/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.cs new file mode 100644 index 000000000000..db2b091004b7 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class AddCipherReportExclusions : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ReportExclusions", + table: "Cipher", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ReportExclusions", + table: "Cipher"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 6cc3a6f84224..2bba2f0967aa 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3349,6 +3349,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("char(36)"); + b.Property("ReportExclusions") + .HasColumnType("longtext"); + b.Property("Reprompt") .HasColumnType("tinyint unsigned"); diff --git a/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs b/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs new file mode 100644 index 000000000000..0edb37a0211e --- /dev/null +++ b/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs @@ -0,0 +1,4635 @@ +// +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("20261021171844_AddCipherReportExclusions")] + partial class AddCipherReportExclusions + { + /// + 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.11") + .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.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessRuleId") + .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("AccessRuleId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.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.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("ExemptFromBillingAutomation") + .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("MaxAutoscalePamSeats") + .HasColumnType("integer"); + + 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("OrganizationKeyId") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("PamSeatMinimum") + .HasColumnType("integer"); + + b.Property("PamSeats") + .HasColumnType("integer"); + + 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("UseInviteLinks") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseMyItems") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePam") + .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("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.OrganizationInviteLink", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowedDomains") + .IsRequired() + .HasColumnType("text"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Invite") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsConfirmation") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInviteLink", (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") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + 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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.OrganizationPlanMigrationCohort", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ChurnDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MigrationPathId") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProactiveDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Name") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohort", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohortAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ChurnDiscountAppliedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CohortId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("MigratedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RenewalNotificationSentDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ScheduledDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "CreationDate", "Id") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "ScheduledDate", "MigratedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohortAssignment", (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.OrganizationDeleteTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CompletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureCount") + .HasColumnType("integer"); + + b.Property("ItemsDeletedCount") + .HasColumnType("bigint"); + + b.Property("LastError") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskType") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CompletedDate", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationDeleteTask", (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("ReportFile") + .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.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.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ClientVersion") + .HasMaxLength(43) + .HasColumnType("character varying(43)"); + + 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("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + 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("SendId") + .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("OrganizationId") + .HasDatabaseName("IX_Event_OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "SendId", "Date") + .HasDatabaseName("IX_Event_OrganizationIdSendIdDate") + .HasAnnotation("SqlServer:Clustered", false); + + 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("AccessPam") + .HasColumnType("boolean"); + + 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("RevocationReason") + .HasColumnType("smallint"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StatusNew") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("V2UpgradeToken") + .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("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("ProviderId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ProviderId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganizationOrProvider", "((CASE WHEN \"UserId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"OrganizationId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"ProviderId\" IS NOT NULL THEN 1 ELSE 0 END)) = 1"); + }); + }); + + 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") + .IsRequired() + .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") + .IsRequired() + .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("LastApiKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + 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("MasterPasswordSalt") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + 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("UserKeyId") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("V2UpgradeToken") + .HasColumnType("text"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.Pam.Models.AccessAuditEvent", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessConnectorId") + .HasColumnType("uuid"); + + b.Property("AccessConnectorName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("AccessLeaseId") + .HasColumnType("uuid"); + + b.Property("AccessRequestId") + .HasColumnType("uuid"); + + b.Property("AccessRuleId") + .HasColumnType("uuid"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorId") + .HasColumnType("uuid"); + + b.Property("ActorName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CorrelationId") + .HasColumnType("uuid"); + + b.Property("Detail") + .HasColumnType("text"); + + b.Property("Kind") + .HasColumnType("smallint"); + + b.Property("LeaseNotAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("LeaseNotBefore") + .HasColumnType("timestamp with time zone"); + + b.Property("OccurredDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phase") + .HasColumnType("smallint"); + + b.Property("RequesterEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RequesterId") + .HasColumnType("uuid"); + + b.Property("RequesterName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RotationConfigId") + .HasColumnType("uuid"); + + b.Property("RotationJobId") + .HasColumnType("uuid"); + + b.Property("RotationSource") + .HasColumnType("smallint"); + + b.Property("RuleName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SyncState") + .HasColumnType("smallint"); + + b.Property("TargetSystemId") + .HasColumnType("uuid"); + + b.Property("TargetSystemName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("OrganizationId", "OccurredDate", "Id") + .IsDescending(false, true, true); + + b.ToTable("AccessAuditEvent", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessDecision", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessRequestId") + .HasColumnType("uuid"); + + b.Property("ApproverId") + .HasColumnType("uuid"); + + b.Property("Comment") + .HasColumnType("text"); + + b.Property("ConditionKind") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeciderKind") + .HasColumnType("smallint"); + + b.Property("EvaluationContext") + .HasColumnType("text"); + + b.Property("Verdict") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId"); + + b.ToTable("AccessDecision", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessRequestId") + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("smallint"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NotAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("NotBefore") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RequesterId") + .HasColumnType("uuid"); + + b.Property("RevokedBy") + .HasColumnType("uuid"); + + b.Property("RevokedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.HasIndex("CollectionId", "Action"); + + b.HasIndex("NotAfter", "Action"); + + b.HasIndex("CipherId", "Action", "NotAfter") + .IsDescending(false, false, true); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessLease", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("smallint"); + + b.Property("ActionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtensionOfLeaseId") + .HasColumnType("uuid"); + + b.Property("NotAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("NotBefore") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("RequesterId") + .HasColumnType("uuid"); + + b.Property("RuleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ExtensionOfLeaseId"); + + b.HasIndex("RuleId"); + + b.HasIndex("CollectionId", "CreationDate"); + + b.HasIndex("OrganizationId", "Action"); + + b.HasIndex("RequesterId", "CreationDate"); + + b.HasIndex("CollectionId", "Action", "NotAfter"); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowsExtensions") + .HasColumnType("boolean"); + + b.Property("Conditions") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultLeaseDurationSeconds") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("LastEditedBy") + .HasColumnType("uuid"); + + b.Property("MaxExtensionDurationSeconds") + .HasColumnType("integer"); + + b.Property("MaxLeaseDurationSeconds") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SingleActiveLease") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("AccessRule", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKeyId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastHeartbeatDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamAccessConnector", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessConnectorId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("TargetSystemId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.HasIndex("AccessConnectorId", "TargetSystemId") + .IsUnique(); + + b.ToTable("PamAccessConnectorTargetAssignment", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.Property("AccessLeaseId") + .HasColumnType("uuid"); + + b.Property("SweptDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("AccessLeaseId"); + + b.ToTable("PamLeaseExpirySweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("smallint"); + + b.Property("CipherUpdated") + .HasColumnType("boolean"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureReason") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("JobId") + .HasColumnType("uuid"); + + b.Property("ResolvedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionTermination") + .HasColumnType("smallint"); + + b.Property("SyncState") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedByAccessConnectorId", "CreationDate") + .IsDescending(false, true); + + b.HasIndex("JobId", "Action"); + + b.ToTable("PamRotationAttempt", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("LastRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RotateOnAccessEnd") + .HasColumnType("boolean"); + + b.Property("ScheduleCron") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SealedData") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("TargetSystemId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CipherId") + .IsUnique(); + + b.HasIndex("NextRotationDate"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.ToTable("PamRotationConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("smallint"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("uuid"); + + b.Property("ClaimedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextClaimableDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RotationConfigId") + .HasColumnType("uuid"); + + b.Property("Source") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("Action", "ExpirationDate"); + + b.HasIndex("ClaimedByAccessConnectorId", "Action"); + + b.HasIndex("RotationConfigId", "Action"); + + b.ToTable("PamRotationJob", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.Property("RotationJobId") + .HasColumnType("uuid"); + + b.Property("SweptDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("RotationJobId"); + + b.ToTable("PamRotationJobTimeoutSweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Method") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SealedData") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamTargetSystem", (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("ReportExclusions") + .HasColumnType("text"); + + 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.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("AccessRuleId") + .OnDelete(DeleteBehavior.Restrict); + + 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.AdminConsole.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationInviteLink", 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.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.OrganizationPlanMigrationCohortAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohort", "Cohort") + .WithMany() + .HasForeignKey("CohortId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cohort"); + + 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.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + 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.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + 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.Pam.Models.AccessAuditEvent", 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.Pam.Models.AccessDecision", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("ExtensionOfLeaseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("RuleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", 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.Pam.Models.PamAccessConnector", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", null) + .WithMany() + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", null) + .WithMany() + .HasForeignKey("AccessConnectorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("AccessLeaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", null) + .WithMany() + .HasForeignKey("RotationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("RotationJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", 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.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.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + 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.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/20261021171844_AddCipherReportExclusions.cs b/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.cs new file mode 100644 index 000000000000..6b279b80e2ae --- /dev/null +++ b/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class AddCipherReportExclusions : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ReportExclusions", + table: "Cipher", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ReportExclusions", + table: "Cipher"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index db4d63071ac2..4f333304b1df 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3355,6 +3355,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("uuid"); + b.Property("ReportExclusions") + .HasColumnType("text"); + b.Property("Reprompt") .HasColumnType("smallint"); diff --git a/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs b/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs new file mode 100644 index 000000000000..4f1613d3303a --- /dev/null +++ b/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs @@ -0,0 +1,4618 @@ +// +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("20261021171852_AddCipherReportExclusions")] + partial class AddCipherReportExclusions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.11"); + + 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.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessRuleId") + .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("AccessRuleId"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.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.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("ExemptFromBillingAutomation") + .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("MaxAutoscalePamSeats") + .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("OrganizationKeyId") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("PamSeatMinimum") + .HasColumnType("INTEGER"); + + b.Property("PamSeats") + .HasColumnType("INTEGER"); + + 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("UseInviteLinks") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseMyItems") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePam") + .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("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationInviteLink", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowedDomains") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Invite") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsConfirmation") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInviteLink", (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") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.OrganizationPlanMigrationCohort", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ChurnDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("MigrationPathId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ProactiveDiscountCouponCode") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Name") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohort", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohortAssignment", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ChurnDiscountAppliedDate") + .HasColumnType("TEXT"); + + b.Property("CohortId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("MigratedDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RenewalNotificationSentDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("ScheduledDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "CreationDate", "Id") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("CohortId", "ScheduledDate", "MigratedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationPlanMigrationCohortAssignment", (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.OrganizationDeleteTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CompletedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("FailureCount") + .HasColumnType("INTEGER"); + + b.Property("ItemsDeletedCount") + .HasColumnType("INTEGER"); + + b.Property("LastError") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("TaskType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CompletedDate", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationDeleteTask", (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("ReportFile") + .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.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.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("ClientVersion") + .HasMaxLength(43) + .HasColumnType("TEXT"); + + 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("LastActivityDate") + .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("SendId") + .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("OrganizationId") + .HasDatabaseName("IX_Event_OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "SendId", "Date") + .HasDatabaseName("IX_Event_OrganizationIdSendIdDate") + .HasAnnotation("SqlServer:Clustered", false); + + 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("AccessPam") + .HasColumnType("INTEGER"); + + 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("RevocationReason") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("StatusNew") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("V2UpgradeToken") + .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("ProviderId") + .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("ProviderId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganizationOrProvider", "((CASE WHEN \"UserId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"OrganizationId\" IS NOT NULL THEN 1 ELSE 0 END) + (CASE WHEN \"ProviderId\" IS NOT NULL THEN 1 ELSE 0 END)) = 1"); + }); + }); + + 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") + .IsRequired() + .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") + .IsRequired() + .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("LastApiKeyRotationDate") + .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("MasterPasswordSalt") + .HasMaxLength(256) + .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("UserKeyId") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("V2UpgradeToken") + .HasColumnType("TEXT"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + 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.Pam.Models.AccessAuditEvent", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessConnectorId") + .HasColumnType("TEXT"); + + b.Property("AccessConnectorName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("AccessLeaseId") + .HasColumnType("TEXT"); + + b.Property("AccessRequestId") + .HasColumnType("TEXT"); + + b.Property("AccessRuleId") + .HasColumnType("TEXT"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ActorId") + .HasColumnType("TEXT"); + + b.Property("ActorName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CorrelationId") + .HasColumnType("TEXT"); + + b.Property("Detail") + .HasColumnType("TEXT"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("LeaseNotAfter") + .HasColumnType("TEXT"); + + b.Property("LeaseNotBefore") + .HasColumnType("TEXT"); + + b.Property("OccurredDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Phase") + .HasColumnType("INTEGER"); + + b.Property("RequesterEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RequesterId") + .HasColumnType("TEXT"); + + b.Property("RequesterName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RotationConfigId") + .HasColumnType("TEXT"); + + b.Property("RotationJobId") + .HasColumnType("TEXT"); + + b.Property("RotationSource") + .HasColumnType("INTEGER"); + + b.Property("RuleName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("SyncState") + .HasColumnType("INTEGER"); + + b.Property("TargetSystemId") + .HasColumnType("TEXT"); + + b.Property("TargetSystemName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("OrganizationId", "OccurredDate", "Id") + .IsDescending(false, true, true); + + b.ToTable("AccessAuditEvent", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessDecision", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessRequestId") + .HasColumnType("TEXT"); + + b.Property("ApproverId") + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("ConditionKind") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeciderKind") + .HasColumnType("INTEGER"); + + b.Property("EvaluationContext") + .HasColumnType("TEXT"); + + b.Property("Verdict") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId"); + + b.ToTable("AccessDecision", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessRequestId") + .HasColumnType("TEXT"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("NotAfter") + .HasColumnType("TEXT"); + + b.Property("NotBefore") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RequesterId") + .HasColumnType("TEXT"); + + b.Property("RevokedBy") + .HasColumnType("TEXT"); + + b.Property("RevokedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessRequestId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.HasIndex("CollectionId", "Action"); + + b.HasIndex("NotAfter", "Action"); + + b.HasIndex("CipherId", "Action", "NotAfter") + .IsDescending(false, false, true); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessLease", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("ActionDate") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExtensionOfLeaseId") + .HasColumnType("TEXT"); + + b.Property("NotAfter") + .HasColumnType("TEXT"); + + b.Property("NotBefore") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reason") + .HasColumnType("TEXT"); + + b.Property("RequesterId") + .HasColumnType("TEXT"); + + b.Property("RuleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExtensionOfLeaseId"); + + b.HasIndex("RuleId"); + + b.HasIndex("CollectionId", "CreationDate"); + + b.HasIndex("OrganizationId", "Action"); + + b.HasIndex("RequesterId", "CreationDate"); + + b.HasIndex("CollectionId", "Action", "NotAfter"); + + b.HasIndex("RequesterId", "CipherId", "Action"); + + b.ToTable("AccessRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowsExtensions") + .HasColumnType("INTEGER"); + + b.Property("Conditions") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultLeaseDurationSeconds") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("LastEditedBy") + .HasColumnType("TEXT"); + + b.Property("MaxExtensionDurationSeconds") + .HasColumnType("INTEGER"); + + b.Property("MaxLeaseDurationSeconds") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SingleActiveLease") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("AccessRule", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKeyId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("LastHeartbeatDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId") + .IsUnique(); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamAccessConnector", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessConnectorId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("TargetSystemId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.HasIndex("AccessConnectorId", "TargetSystemId") + .IsUnique(); + + b.ToTable("PamAccessConnectorTargetAssignment", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.Property("AccessLeaseId") + .HasColumnType("TEXT"); + + b.Property("SweptDate") + .HasColumnType("TEXT"); + + b.HasKey("AccessLeaseId"); + + b.ToTable("PamLeaseExpirySweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("CipherUpdated") + .HasColumnType("INTEGER"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("FailureReason") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("JobId") + .HasColumnType("TEXT"); + + b.Property("ResolvedDate") + .HasColumnType("TEXT"); + + b.Property("SessionTermination") + .HasColumnType("INTEGER"); + + b.Property("SyncState") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedByAccessConnectorId", "CreationDate") + .IsDescending(false, true); + + b.HasIndex("JobId", "Action"); + + b.ToTable("PamRotationAttempt", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("LastRotationDate") + .HasColumnType("TEXT"); + + b.Property("NextRotationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("RotateOnAccessEnd") + .HasColumnType("INTEGER"); + + b.Property("ScheduleCron") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SealedData") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("TargetSystemId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CipherId") + .IsUnique(); + + b.HasIndex("NextRotationDate"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("TargetSystemId"); + + b.ToTable("PamRotationConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Action") + .HasColumnType("INTEGER"); + + b.Property("ClaimedByAccessConnectorId") + .HasColumnType("TEXT"); + + b.Property("ClaimedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("NextClaimableDate") + .HasColumnType("TEXT"); + + b.Property("RotationConfigId") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Action", "ExpirationDate"); + + b.HasIndex("ClaimedByAccessConnectorId", "Action"); + + b.HasIndex("RotationConfigId", "Action"); + + b.ToTable("PamRotationJob", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.Property("RotationJobId") + .HasColumnType("TEXT"); + + b.Property("SweptDate") + .HasColumnType("TEXT"); + + b.HasKey("RotationJobId"); + + b.ToTable("PamRotationJobTimeoutSweep", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Method") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SealedData") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("PamTargetSystem", (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("ReportExclusions") + .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.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("AccessRuleId") + .OnDelete(DeleteBehavior.Restrict); + + 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.AdminConsole.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationInviteLink", 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.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.OrganizationPlanMigrationCohortAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationPlanMigrationCohort", "Cohort") + .WithMany() + .HasForeignKey("CohortId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cohort"); + + 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.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + 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.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + 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.Pam.Models.AccessAuditEvent", 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.Pam.Models.AccessDecision", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", null) + .WithMany() + .HasForeignKey("AccessRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("ExtensionOfLeaseId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", null) + .WithMany() + .HasForeignKey("RuleId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.AccessRule", 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.Pam.Models.PamAccessConnector", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", null) + .WithMany() + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnectorTargetAssignment", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamAccessConnector", null) + .WithMany() + .HasForeignKey("AccessConnectorId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamLeaseExpirySweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.AccessLease", null) + .WithMany() + .HasForeignKey("AccessLeaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationAttempt", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", null) + .WithMany() + .HasForeignKey("TargetSystemId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationConfig", null) + .WithMany() + .HasForeignKey("RotationConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJobTimeoutSweep", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Pam.Models.PamRotationJob", null) + .WithMany() + .HasForeignKey("RotationJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Pam.Models.PamTargetSystem", 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.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.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + 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.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/20261021171852_AddCipherReportExclusions.cs b/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.cs new file mode 100644 index 000000000000..8b40abb161af --- /dev/null +++ b/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class AddCipherReportExclusions : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ReportExclusions", + table: "Cipher", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ReportExclusions", + table: "Cipher"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 31beafa10726..e878eafbe413 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3338,6 +3338,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("TEXT"); + b.Property("ReportExclusions") + .HasColumnType("TEXT"); + b.Property("Reprompt") .HasColumnType("INTEGER"); From 1ffa0450b24cd6c46dd04801ac512f08b9e24618 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:25:02 -0400 Subject: [PATCH 02/27] [PM-41931] feat(dirt): Return the per-user report exclusion date on ciphers Reads this user's entry from ReportExclusions into CipherDetails and the cipher response, so the standard sync delivers every exclusion. EF replaces never write the column. --- .../Models/Response/CipherResponseModel.cs | 2 + src/Core/Vault/Models/Data/CipherDetails.cs | 3 + .../Queries/UserCipherDetailsQuery.cs | 30 +- .../Vault/Repositories/CipherRepository.cs | 17 + .../Queries/CipherDetailsQuery.cs | 3 + src/Sql/dbo/Vault/Functions/CipherDetails.sql | 9 +- .../Cipher/CipherDetails_Create.sql | 4 +- .../CipherDetails_CreateWithCollections.sql | 2 + .../Cipher/CipherDetails_ReadByIdUserId.sql | 4 +- .../Cipher/CipherDetails_Update.sql | 4 +- ...026-10-21_00_AddCipherReportExclusions.sql | 309 ++++++++++++++++++ 11 files changed, 380 insertions(+), 7 deletions(-) diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index 3adbbb4748e3..8ab8e5fa4404 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -296,6 +296,7 @@ protected CipherResponseModel( Favorite = cipher.Favorite; Edit = cipher.Edit; ArchivedDate = cipher.ArchivedDate; + ReportExcludedDate = cipher.ReportExcludedDate; ViewPassword = cipher.ViewPassword; Permissions = new CipherPermissionsResponseModel(user, cipher, organizationAbility); } @@ -312,6 +313,7 @@ public static CipherResponseModel From(FullCipherAccess access, CipherDetails ci public bool Edit { get; set; } public bool ViewPassword { get; set; } public DateTime? ArchivedDate { get; set; } + public DateTime? ReportExcludedDate { get; set; } public CipherPermissionsResponseModel Permissions { get; set; } } diff --git a/src/Core/Vault/Models/Data/CipherDetails.cs b/src/Core/Vault/Models/Data/CipherDetails.cs index 56f5aa39bbab..49bb9dc1079e 100644 --- a/src/Core/Vault/Models/Data/CipherDetails.cs +++ b/src/Core/Vault/Models/Data/CipherDetails.cs @@ -11,6 +11,8 @@ public class CipherDetails : CipherOrganizationDetails public bool Manage { get; set; } // Per-user archived date from Archives JSON. public DateTime? ArchivedDate { get; set; } + // Per-user health report exclusion date from ReportExclusions JSON. + public DateTime? ReportExcludedDate { get; set; } public CipherDetails() { } public CipherDetails(CipherOrganizationDetails cipher) @@ -53,6 +55,7 @@ public CipherDetailsWithCollections( Key = cipher.Key; FolderId = cipher.FolderId; ArchivedDate = cipher.ArchivedDate; + ReportExcludedDate = cipher.ReportExcludedDate; Favorite = cipher.Favorite; Edit = cipher.Edit; ViewPassword = cipher.ViewPassword; diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index 67be55f1a958..aeecd8e16d32 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -71,7 +71,8 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = o.UseTotp, c.Reprompt, c.Key, - c.Archives + c.Archives, + c.ReportExclusions }; var query2 = from c in dbContext.Ciphers @@ -95,7 +96,8 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = false, c.Reprompt, c.Key, - c.Archives + c.Archives, + c.ReportExclusions }; var union = query.Union(query2).Select(c => new CipherDetails @@ -117,7 +119,8 @@ from cg in cg_g.DefaultIfEmpty() Manage = c.Manage, OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, - ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }) + ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }), + ReportExcludedDate = GetReportExcludedDate(_userId, new Cipher { Id = c.Id, ReportExclusions = c.ReportExclusions }) }); return union; } @@ -143,6 +146,27 @@ from cg in cg_g.DefaultIfEmpty() } } + private static DateTime? GetReportExcludedDate(Guid? userId, Cipher cipher) + { + try + { + if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.ReportExclusions)) + { + var exclusions = JsonSerializer.Deserialize>(cipher.ReportExclusions); + if (exclusions.TryGetValue(userId.Value, out var excludedDate)) + { + return excludedDate; + } + } + + return null; + } + catch + { + return null; + } + } + private static Guid? GetFolderId(Guid? userId, Cipher cipher) { try diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 34b2192448db..010261a7cffc 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -574,6 +574,22 @@ await idsToMove.ForEachAsync(cipher => } } + public override async Task ReplaceAsync(Core.Vault.Entities.Cipher cipher) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.Ciphers.FindAsync(cipher.Id); + if (entity != null) + { + var mappedEntity = Mapper.Map(cipher); + dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); + dbContext.Entry(entity).Property(c => c.ReportExclusions).IsModified = false; + await dbContext.SaveChangesAsync(); + } + } + } + public async Task ReplaceAsync(CipherDetails cipher) { using (var scope = ServiceScopeFactory.CreateScope()) @@ -647,6 +663,7 @@ public async Task ReplaceAsync(CipherDetails cipher) var mappedEntity = Mapper.Map(cipher); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); + dbContext.Entry(entity).Property(c => c.ReportExclusions).IsModified = false; if (cipher.OrganizationId.HasValue) { diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs index 43f368b23227..fa471cf5e72f 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs @@ -37,6 +37,9 @@ public virtual IQueryable Run(DatabaseContext dbContext) ArchivedDate = !_userId.HasValue || c.Archives == null || !c.Archives.ToLowerInvariant().Contains(_userId.Value.ToString()) ? null : CoreHelpers.LoadClassFromJsonData>(c.Archives)[_userId.Value], + ReportExcludedDate = !_userId.HasValue || c.ReportExclusions == null || !c.ReportExclusions.ToLowerInvariant().Contains(_userId.Value.ToString()) ? + null : + CoreHelpers.LoadClassFromJsonData>(c.ReportExclusions)[_userId.Value], }; return query; } diff --git a/src/Sql/dbo/Vault/Functions/CipherDetails.sql b/src/Sql/dbo/Vault/Functions/CipherDetails.sql index 51930ec10e6e..f1b700889d3d 100644 --- a/src/Sql/dbo/Vault/Functions/CipherDetails.sql +++ b/src/Sql/dbo/Vault/Functions/CipherDetails.sql @@ -34,6 +34,13 @@ SELECT OR C.[Archives] IS NULL THEN NULL ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[Archives], CONCAT('$."', @UserId, '"'))) - END [ArchivedDate] + END [ArchivedDate], + CASE + WHEN + @UserId IS NULL + OR C.[ReportExclusions] IS NULL + THEN NULL + ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"'))) + END [ReportExcludedDate] FROM [dbo].[Cipher] C; diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql index 846f3bcf8a11..4100650cf366 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql @@ -19,7 +19,9 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, - @Archives NVARCHAR(MAX) = NULL -- not used + @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql index da573b1d1395..4e02aeb870bf 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql @@ -20,6 +20,8 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql index 282123010192..4cfa32679187 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql @@ -21,6 +21,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], + [ReportExcludedDate], -- The highest rank holds Manage as 4, Edit as 2 and ViewPassword as 1; each flag is one part of it (MAX(R.[Rank]) / 2) % 2 AS [Edit], MAX(R.[Rank]) % 2 AS [ViewPassword], @@ -48,5 +49,6 @@ SELECT [Reprompt], [Key], [OrganizationUseTotp], - [ArchivedDate] + [ArchivedDate], + [ReportExcludedDate] END diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index 4676f20fd38b..f6c57134418b 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -19,7 +19,9 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, - @Archives NVARCHAR(MAX) = NULL -- not used + @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql index 1fbfebeb7094..10767da37789 100644 --- a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql @@ -5,6 +5,60 @@ BEGIN END GO +CREATE OR ALTER FUNCTION [dbo].[CipherDetails](@UserId UNIQUEIDENTIFIER) +RETURNS TABLE +AS RETURN +SELECT + C.[Id], + C.[UserId], + C.[OrganizationId], + C.[Type], + C.[Data], + C.[Attachments], + C.[CreationDate], + C.[RevisionDate], + CASE + WHEN + @UserId IS NULL + OR C.[Favorites] IS NULL + OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL + THEN 0 + ELSE 1 + END [Favorite], + CASE + WHEN + @UserId IS NULL + OR C.[Folders] IS NULL + THEN NULL + ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"'))) + END [FolderId], + C.[DeletedDate], + C.[Reprompt], + C.[Key], + CASE + WHEN + @UserId IS NULL + OR C.[Archives] IS NULL + THEN NULL + ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[Archives], CONCAT('$."', @UserId, '"'))) + END [ArchivedDate], + CASE + WHEN + @UserId IS NULL + OR C.[ReportExclusions] IS NULL + THEN NULL + ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"'))) + END [ReportExcludedDate] +FROM + [dbo].[Cipher] C; +GO + +IF OBJECT_ID('[dbo].[UserCipherDetails]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[UserCipherDetails]'; +END +GO + CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create] @Id UNIQUEIDENTIFIER OUTPUT, @UserId UNIQUEIDENTIFIER, @@ -219,5 +273,260 @@ BEGIN END GO +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), -- not used + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @ViewPassword BIT, -- not used + @Manage BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @ArchivedDate DATETIME2(7) = NULL, + @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL -- not used +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"') + DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey) + + INSERT INTO [dbo].[Cipher] + ( + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Favorites], + [Folders], + [CreationDate], + [RevisionDate], + [DeletedDate], + [Reprompt], + [Key], + [Archives] + ) + VALUES + ( + @Id, + CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + @OrganizationId, + @Type, + @Data, + CASE WHEN @Favorite = 1 THEN CONCAT('{', @UserIdKey, ':true}') ELSE NULL END, + CASE WHEN @FolderId IS NOT NULL THEN CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') ELSE NULL END, + @CreationDate, + @RevisionDate, + @DeletedDate, + @Reprompt, + @Key, + CASE WHEN @ArchivedDate IS NOT NULL THEN CONCAT('{', @UserIdKey, ':"', CONVERT(NVARCHAR(30), @ArchivedDate, 127), '"}') ELSE NULL END + ) + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), -- not used + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @ViewPassword BIT, -- not used + @Manage BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @ArchivedDate DATETIME2(7) = NULL, + @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, + @Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @ViewPassword, @Manage, + @OrganizationUseTotp, @DeletedDate, @Reprompt, @Key, @ArchivedDate + + DECLARE @UpdateCollectionsSuccess INT + EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds + + -- Bump the account revision date AFTER collections are assigned. + IF @UpdateCollectionsSuccess = 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @ViewPassword BIT, -- not used + @Manage BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @DeletedDate DATETIME2(2), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL, + @ArchivedDate DATETIME2(7) = NULL, + @Archives NVARCHAR(MAX) = NULL, -- not used + @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ReportExclusions NVARCHAR(MAX) = NULL -- not used +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"') + DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey) + + UPDATE + [dbo].[Cipher] + SET + [UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + [OrganizationId] = @OrganizationId, + [Type] = @Type, + [Data] = @Data, + [Folders] = + CASE + WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN + CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') + WHEN @FolderId IS NOT NULL THEN + JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50))) + ELSE + JSON_MODIFY([Folders], @UserIdPath, NULL) + END, + [Favorites] = + CASE + WHEN @Favorite = 1 AND [Favorites] IS NULL THEN + CONCAT('{', @UserIdKey, ':true}') + WHEN @Favorite = 1 THEN + JSON_MODIFY([Favorites], @UserIdPath, CAST(1 AS BIT)) + ELSE + JSON_MODIFY([Favorites], @UserIdPath, NULL) + END, + [Archives] = + CASE + WHEN @ArchivedDate IS NOT NULL AND [Archives] IS NULL THEN + CONCAT('{', @UserIdKey, ':"', CONVERT(NVARCHAR(30), @ArchivedDate, 127), '"}') + WHEN @ArchivedDate IS NOT NULL THEN + JSON_MODIFY([Archives], @UserIdPath, CONVERT(NVARCHAR(30), @ArchivedDate, 127)) + ELSE + JSON_MODIFY([Archives], @UserIdPath, NULL) + END, + [Attachments] = @Attachments, + [Reprompt] = @Reprompt, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [DeletedDate] = @DeletedDate, + [Key] = @Key + WHERE + [Id] = @Id + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_ReadByIdUserId] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp], + [ArchivedDate], + [ReportExcludedDate], + MAX ([Edit]) AS [Edit], + MAX ([ViewPassword]) AS [ViewPassword], + MAX ([Manage]) AS [Manage] + FROM + [dbo].[UserCipherDetails](@UserId) + WHERE + [Id] = @Id + GROUP BY + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp], + [ArchivedDate], + [ReportExcludedDate] +END +GO + EXECUTE sp_refreshview N'[dbo].[CipherView]' GO From 562b531a6908afba812ebeca3836becb998115f6 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:27:02 -0400 Subject: [PATCH 03/27] [PM-41931] feat(dirt): Add the cipher report exclusion repository DIRT-owned writer for Cipher.ReportExclusions. Only the user's own personal, not-deleted logins can be excluded, and only the account revision is bumped, so other devices sync without an edit conflict. --- .../ICipherReportExclusionRepository.cs | 7 +++ .../DapperServiceCollectionExtensions.cs | 1 + .../Dirt/CipherReportExclusionRepository.cs | 25 ++++++++++ .../Dirt/CipherReportExclusionRepository.cs | 46 +++++++++++++++++++ ...ityFrameworkServiceCollectionExtensions.cs | 1 + .../Cipher_ExcludeFromReports.sql | 35 ++++++++++++++ ...026-10-21_00_AddCipherReportExclusions.sql | 37 +++++++++++++++ 7 files changed, 152 insertions(+) create mode 100644 src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs create mode 100644 src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql diff --git a/src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs b/src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs new file mode 100644 index 000000000000..ac17ede1b67b --- /dev/null +++ b/src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Dirt.Repositories; + +public interface ICipherReportExclusionRepository +{ + /// Excludes the user's personal, not-deleted logins from health reports. Returns how many were excluded. + Task ExcludeAsync(IEnumerable cipherIds, Guid userId); +} diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 9f9d3c6f5bdc..9912bc6ca8b9 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -96,6 +96,7 @@ public static void AddDapperRepositories(this IServiceCollection services, bool services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs new file mode 100644 index 000000000000..5214c0dbfa66 --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs @@ -0,0 +1,25 @@ +using System.Data; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class CipherReportExclusionRepository : BaseRepository, ICipherReportExclusionRepository +{ + public CipherReportExclusionRepository(GlobalSettings globalSettings) + : base(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) + { + await using var connection = new SqlConnection(ConnectionString); + return await connection.ExecuteScalarAsync( + "[dbo].[Cipher_ExcludeFromReports]", + new { Ids = cipherIds.ToGuidIdArrayTVP(), UserId = userId }, + commandType: CommandType.StoredProcedure); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs new file mode 100644 index 000000000000..272202e39d45 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs @@ -0,0 +1,46 @@ +using AutoMapper; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Utilities; +using Bit.Core.Vault.Enums; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Dirt; + +public class CipherReportExclusionRepository : BaseEntityFrameworkRepository, ICipherReportExclusionRepository +{ + public CipherReportExclusionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper) + { + } + + public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var ids = cipherIds.ToList(); + var ciphers = await dbContext.Ciphers + .Where(c => ids.Contains(c.Id) && c.UserId == userId && c.OrganizationId == null + && c.Type == CipherType.Login && c.DeletedDate == null) + .ToListAsync(); + + var utcNow = DateTime.UtcNow; + foreach (var cipher in ciphers) + { + var exclusions = string.IsNullOrWhiteSpace(cipher.ReportExclusions) + ? new Dictionary() + : CoreHelpers.LoadClassFromJsonData>(cipher.ReportExclusions) + ?? new Dictionary(); + exclusions[userId] = utcNow; + cipher.ReportExclusions = CoreHelpers.ClassToJsonData(exclusions); + } + + if (ciphers.Count > 0) + { + await dbContext.UserBumpAccountRevisionDateAsync(userId); + } + await dbContext.SaveChangesAsync(); + return ciphers.Count; + } +} diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index da2dc1dc108c..2fc87c16cbca 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -132,6 +132,7 @@ public static void AddPasswordManagerEFRepositories(this IServiceCollection serv services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); if (selfHosted) diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql new file mode 100644 index 000000000000..2f873296dd66 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql @@ -0,0 +1,35 @@ +CREATE PROCEDURE [dbo].[Cipher_ExcludeFromReports] + @Ids AS [dbo].[GuidIdArray] READONLY, + @UserId AS UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME(); + + UPDATE + C + SET + [ReportExclusions] = JSON_MODIFY( + COALESCE(C.[ReportExclusions], N'{}'), + CONCAT('$."', @UserId, '"'), + CONVERT(NVARCHAR(30), @UtcNow, 127) + ) + FROM + [dbo].[Cipher] C + INNER JOIN @Ids I ON I.[Id] = C.[Id] + WHERE + C.[UserId] = @UserId + AND C.[OrganizationId] IS NULL + AND C.[Type] = 1 -- Login + AND C.[DeletedDate] IS NULL + + DECLARE @Count INT = @@ROWCOUNT + + IF @Count > 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END + + SELECT @Count +END diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql index 10767da37789..6a6022480a42 100644 --- a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql @@ -528,5 +528,42 @@ SELECT END GO +CREATE OR ALTER PROCEDURE [dbo].[Cipher_ExcludeFromReports] + @Ids AS [dbo].[GuidIdArray] READONLY, + @UserId AS UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME(); + + UPDATE + C + SET + [ReportExclusions] = JSON_MODIFY( + COALESCE(C.[ReportExclusions], N'{}'), + CONCAT('$."', @UserId, '"'), + CONVERT(NVARCHAR(30), @UtcNow, 127) + ) + FROM + [dbo].[Cipher] C + INNER JOIN @Ids I ON I.[Id] = C.[Id] + WHERE + C.[UserId] = @UserId + AND C.[OrganizationId] IS NULL + AND C.[Type] = 1 -- Login + AND C.[DeletedDate] IS NULL + + DECLARE @Count INT = @@ROWCOUNT + + IF @Count > 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END + + SELECT @Count +END +GO + EXECUTE sp_refreshview N'[dbo].[CipherView]' GO From cbc2cb9acb10d8570033b0895ae647ef37cc7550 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:28:34 -0400 Subject: [PATCH 04/27] [PM-41931] feat(dirt): Add the exclude from reports endpoint PUT ciphers/{id}/exclude-from-reports, behind the health report exclusions flag. Requires premium, and pushes a cipher sync so the user's other devices pick up the exclusion. --- .../CipherReportExclusionsController.cs | 33 +++++++++++++++ .../ExcludeCipherFromReportsCommand.cs | 40 +++++++++++++++++++ .../IExcludeCipherFromReportsCommand.cs | 7 ++++ .../ReportingServiceCollectionExtensions.cs | 1 + 4 files changed, 81 insertions(+) create mode 100644 src/Api/Dirt/Controllers/CipherReportExclusionsController.cs create mode 100644 src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs create mode 100644 src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs diff --git a/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs b/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs new file mode 100644 index 000000000000..0a6bab2e3efb --- /dev/null +++ b/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs @@ -0,0 +1,33 @@ +using Bit.Core; +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Services; +using Bitwarden.Server.Sdk.Features; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Dirt.Controllers; + +[Route("ciphers")] +[Authorize("Application")] +public class CipherReportExclusionsController : Controller +{ + private readonly IUserService _userService; + private readonly IExcludeCipherFromReportsCommand _excludeCipherFromReportsCommand; + + public CipherReportExclusionsController( + IUserService userService, + IExcludeCipherFromReportsCommand excludeCipherFromReportsCommand) + { + _userService = userService; + _excludeCipherFromReportsCommand = excludeCipherFromReportsCommand; + } + + /// Excludes the caller's personal login from their health reports. + [HttpPut("{id}/exclude-from-reports")] + [RequireFeature(FeatureFlagKeys.HealthReportExclusions)] + public async Task PutExcludeFromReportsAsync(Guid id) + { + var userId = _userService.GetProperUserId(User)!.Value; + await _excludeCipherFromReportsCommand.ExcludeAsync(id, userId); + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs new file mode 100644 index 000000000000..891f0f1a1c01 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs @@ -0,0 +1,40 @@ +using Bit.Core.Billing.Premium.Queries; +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class ExcludeCipherFromReportsCommand : IExcludeCipherFromReportsCommand +{ + private readonly ICipherReportExclusionRepository _cipherReportExclusionRepository; + private readonly IHasPremiumAccessQuery _hasPremiumAccessQuery; + private readonly IPushNotificationService _pushService; + + public ExcludeCipherFromReportsCommand( + ICipherReportExclusionRepository cipherReportExclusionRepository, + IHasPremiumAccessQuery hasPremiumAccessQuery, + IPushNotificationService pushService) + { + _cipherReportExclusionRepository = cipherReportExclusionRepository; + _hasPremiumAccessQuery = hasPremiumAccessQuery; + _pushService = pushService; + } + + public async Task ExcludeAsync(Guid cipherId, Guid userId) + { + if (!await _hasPremiumAccessQuery.HasPremiumAccessAsync(userId)) + { + throw new BadRequestException("Premium required to exclude items from reports."); + } + + var excluded = await _cipherReportExclusionRepository.ExcludeAsync([cipherId], userId); + if (excluded == 0) + { + throw new NotFoundException(); + } + + await _pushService.PushSyncCiphersAsync(userId, true); + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs new file mode 100644 index 000000000000..fb0d89921645 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; + +public interface IExcludeCipherFromReportsCommand +{ + /// Excludes the user's personal login from their health reports. Requires premium. + Task ExcludeAsync(Guid cipherId, Guid userId); +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs index 6aca3160daca..f2a5e02caf75 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ public static void AddReportingServices(this IServiceCollection services, IGloba services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // v2 file storage commands services.AddScoped(); From 12e2aca1466a4b93016da04927ac7bc54a337b38 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:36:02 -0400 Subject: [PATCH 05/27] [PM-41931] test(dirt): Cover health report exclusions Repository tests run on every database and pin the scope rules, the per-user read, the revision date behavior, and that Vault replaces keep the exclusion. Adds command and controller unit tests. --- .../CipherReportExclusionsControllerTests.cs | 26 ++ .../ExcludeCipherFromReportsCommandTests.cs | 60 +++++ .../CipherReportExclusionRepositoryTests.cs | 247 ++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs create mode 100644 test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs create mode 100644 test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs diff --git a/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs b/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs new file mode 100644 index 000000000000..b7004e157ee6 --- /dev/null +++ b/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs @@ -0,0 +1,26 @@ +using System.Security.Claims; +using Bit.Api.Dirt.Controllers; +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Dirt; + +[ControllerCustomize(typeof(CipherReportExclusionsController))] +[SutProviderCustomize] +public class CipherReportExclusionsControllerTests +{ + [Theory, BitAutoData] + public async Task PutExcludeFromReports_CallsCommandWithCallingUser( + SutProvider sutProvider, Guid cipherId, Guid userId) + { + sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(userId); + + await sutProvider.Sut.PutExcludeFromReportsAsync(cipherId); + + await sutProvider.GetDependency().Received(1).ExcludeAsync(cipherId, userId); + } +} diff --git a/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs new file mode 100644 index 000000000000..4caceac150f8 --- /dev/null +++ b/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs @@ -0,0 +1,60 @@ +using Bit.Core.Billing.Premium.Queries; +using Bit.Core.Dirt.Reports.ReportFeatures; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models; +using Bit.Core.Platform.Push; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Dirt.ReportFeatures; + +[SutProviderCustomize] +public class ExcludeCipherFromReportsCommandTests +{ + [Theory, BitAutoData] + public async Task ExcludeAsync_WithoutPremium_ThrowsBadRequest_AndDoesNotWrite( + SutProvider sutProvider, Guid cipherId, Guid userId) + { + sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.ExcludeAsync(cipherId, userId)); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs().ExcludeAsync(default!, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs().PushAsync(Arg.Any>()); + } + + [Theory, BitAutoData] + public async Task ExcludeAsync_NothingExcluded_ThrowsNotFound_AndDoesNotPush( + SutProvider sutProvider, Guid cipherId, Guid userId) + { + sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(true); + sutProvider.GetDependency() + .ExcludeAsync(Arg.Any>(), userId).Returns(0); + + await Assert.ThrowsAsync(() => sutProvider.Sut.ExcludeAsync(cipherId, userId)); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs().PushAsync(Arg.Any>()); + } + + [Theory, BitAutoData] + public async Task ExcludeAsync_Excluded_PushesCipherSyncExcludingCurrentContext( + SutProvider sutProvider, Guid cipherId, Guid userId) + { + sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(true); + sutProvider.GetDependency() + .ExcludeAsync(Arg.Is>(ids => ids.Single() == cipherId), userId).Returns(1); + + await sutProvider.Sut.ExcludeAsync(cipherId, userId); + + await sutProvider.GetDependency().Received(1) + .PushAsync(Arg.Is>(n => + n.Type == PushType.SyncCiphers && n.TargetId == userId && n.ExcludeCurrentContext)); + } +} diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs new file mode 100644 index 000000000000..23041387241d --- /dev/null +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs @@ -0,0 +1,247 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Enums; +using Bit.Core.Vault.Repositories; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; + +public class CipherReportExclusionRepositoryTests +{ + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersReportExcludedDate( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + + var excluded = await sut.ExcludeAsync([cipher.Id], user.Id); + + Assert.Equal(1, excluded); + var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); + Assert.NotNull(details.ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, + IOrganizationRepository organizationRepository) + { + var user = await CreateUserAsync(userRepository); + var org = await CreateOrganizationAsync(organizationRepository, user); + var cipher = await cipherRepository.CreateAsync(new Cipher + { + Type = CipherType.Login, + UserId = user.Id, + OrganizationId = org.Id, + Data = "", + }); + + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id, CipherType.SecureNote); + + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_DeletedCipher_ReturnsZero( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); + + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var owner = await CreateUserAsync(userRepository); + var otherUser = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, owner.Id); + + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id)); + Assert.Null((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_AlreadyExcluded_ReturnsOne( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id); + var firstDate = (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate; + + Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id)); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate >= firstDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + var before = (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).RevisionDate; + + await sut.ExcludeAsync([cipher.Id], user.Id); + + Assert.Equal(before, (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).RevisionDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_BumpsAccountRevisionDate( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + var before = (await userRepository.GetByIdAsync(user.Id))!.AccountRevisionDate; + + await sut.ExcludeAsync([cipher.Id], user.Id); + + Assert.True((await userRepository.GetByIdAsync(user.Id))!.AccountRevisionDate > before); + } + + [DatabaseTheory, DatabaseData] + public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var excludedCipher = await CreateCipherAsync(cipherRepository, user.Id); + var otherCipher = await CreateCipherAsync(cipherRepository, user.Id); + + await sut.ExcludeAsync([excludedCipher.Id], user.Id); + + var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); + Assert.NotNull(ciphers.Single(c => c.Id == excludedCipher.Id).ReportExcludedDate); + Assert.Null(ciphers.Single(c => c.Id == otherCipher.Id).ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id); + + var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); + details.Data = "{\"updated\":true}"; + await cipherRepository.ReplaceAsync(details); + + Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task ReplaceAsync_Cipher_PreservesReportExclusions( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id); + + cipher.Data = "{\"updated\":true}"; + await cipherRepository.ReplaceAsync(cipher); + + Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task SoftDeleteAsync_KeepsReportExclusion( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id); + + await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); + + Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + } + + [DatabaseTheory, DatabaseData] + public async Task DeleteAsync_RemovesExclusion( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await CreateUserAsync(userRepository); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id); + + await cipherRepository.DeleteAsync(cipher); + + Assert.DoesNotContain(await cipherRepository.GetManyByUserIdAsync(user.Id), c => c.Id == cipher.Id); + } + + [DatabaseTheory, DatabaseData] + public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, + IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + var owner = await CreateUserAsync(userRepository); + var member = await CreateUserAsync(userRepository); + var org = await CreateOrganizationAsync(organizationRepository, owner); + var ownerOrgUser = await CreateOrgUserAsync(organizationUserRepository, org, owner, OrganizationUserType.Owner); + var memberOrgUser = await CreateOrgUserAsync(organizationUserRepository, org, member, OrganizationUserType.User); + var collection = await collectionRepository.CreateAsync(new Collection { Name = "Shared", OrganizationId = org.Id }); + await collectionRepository.UpdateUsersAsync(collection.Id, new List + { + new() { Id = ownerOrgUser.Id, Manage = true }, + new() { Id = memberOrgUser.Id, Manage = true }, + }); + var cipher = await CreateCipherAsync(cipherRepository, owner.Id); + await sut.ExcludeAsync([cipher.Id], owner.Id); + + cipher.OrganizationId = org.Id; + await cipherRepository.ReplaceAsync(cipher, [collection.Id]); + + Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ReportExcludedDate); + Assert.Null((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ReportExcludedDate); + } + + private static Task CreateUserAsync(IUserRepository userRepository) => + userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + private static Task CreateCipherAsync(ICipherRepository cipherRepository, Guid userId, + CipherType type = CipherType.Login) => + cipherRepository.CreateAsync(new Cipher { Type = type, UserId = userId, Data = "" }); + + private static Task CreateOrganizationAsync(IOrganizationRepository organizationRepository, User user) => + organizationRepository.CreateAsync(new Organization + { + Name = "Test Organization", + BillingEmail = user.Email, + Plan = "Test", + }); + + private static Task CreateOrgUserAsync(IOrganizationUserRepository organizationUserRepository, + Organization org, User user, OrganizationUserType type) => + organizationUserRepository.CreateAsync(new OrganizationUser + { + UserId = user.Id, + OrganizationId = org.Id, + Status = OrganizationUserStatusType.Confirmed, + Type = type, + }); +} From 72dc897a6f90f85e2748a26d4fc9dfb95ad44798 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:39:53 -0400 Subject: [PATCH 06/27] fixup! [PM-41931] feat(dirt): Add the cipher report exclusion repository --- .../dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql | 4 +--- .../DbScripts/2026-10-21_00_AddCipherReportExclusions.sql | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql index 2f873296dd66..372561b58931 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql @@ -5,15 +5,13 @@ AS BEGIN SET NOCOUNT ON - DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME(); - UPDATE C SET [ReportExclusions] = JSON_MODIFY( COALESCE(C.[ReportExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CONVERT(NVARCHAR(30), @UtcNow, 127) + CONVERT(NVARCHAR(30), SYSUTCDATETIME(), 127) ) FROM [dbo].[Cipher] C diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql index 6a6022480a42..cb1e0702127a 100644 --- a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql @@ -535,15 +535,13 @@ AS BEGIN SET NOCOUNT ON - DECLARE @UtcNow DATETIME2(7) = SYSUTCDATETIME(); - UPDATE C SET [ReportExclusions] = JSON_MODIFY( COALESCE(C.[ReportExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CONVERT(NVARCHAR(30), @UtcNow, 127) + CONVERT(NVARCHAR(30), SYSUTCDATETIME(), 127) ) FROM [dbo].[Cipher] C From a7cb596e8879c84fc5632d41a5b07a5cb6bfda12 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 13:40:12 -0400 Subject: [PATCH 07/27] fixup! [PM-41931] test(dirt): Cover health report exclusions --- .../CipherReportExclusionRepositoryTests.cs | 84 +++++-------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs index 23041387241d..9b63369a515a 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs @@ -1,12 +1,11 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Enums; using Bit.Core.Vault.Repositories; +using Bit.Infrastructure.IntegrationTest.AdminConsole; using Xunit; namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; @@ -17,7 +16,7 @@ public class CipherReportExclusionRepositoryTests public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersReportExcludedDate( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); var excluded = await sut.ExcludeAsync([cipher.Id], user.Id); @@ -32,8 +31,8 @@ public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, IOrganizationRepository organizationRepository) { - var user = await CreateUserAsync(userRepository); - var org = await CreateOrganizationAsync(organizationRepository, user); + var user = await userRepository.CreateTestUserAsync(); + var org = await organizationRepository.CreateTestOrganizationAsync(); var cipher = await cipherRepository.CreateAsync(new Cipher { Type = CipherType.Login, @@ -49,7 +48,7 @@ public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id, CipherType.SecureNote); Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); @@ -59,7 +58,7 @@ public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( public async Task ExcludeAsync_DeletedCipher_ReturnsZero( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); @@ -70,8 +69,8 @@ public async Task ExcludeAsync_DeletedCipher_ReturnsZero( public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var owner = await CreateUserAsync(userRepository); - var otherUser = await CreateUserAsync(userRepository); + var owner = await userRepository.CreateTestUserAsync(); + var otherUser = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, owner.Id); Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id)); @@ -82,20 +81,18 @@ public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( public async Task ExcludeAsync_AlreadyExcluded_ReturnsOne( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); await sut.ExcludeAsync([cipher.Id], user.Id); - var firstDate = (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate; Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id)); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate >= firstDate); } [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); var before = (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).RevisionDate; @@ -108,7 +105,7 @@ public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( public async Task ExcludeAsync_BumpsAccountRevisionDate( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); var before = (await userRepository.GetByIdAsync(user.Id))!.AccountRevisionDate; @@ -121,7 +118,7 @@ public async Task ExcludeAsync_BumpsAccountRevisionDate( public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var excludedCipher = await CreateCipherAsync(cipherRepository, user.Id); var otherCipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -136,7 +133,7 @@ public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); await sut.ExcludeAsync([cipher.Id], user.Id); @@ -151,7 +148,7 @@ public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( public async Task ReplaceAsync_Cipher_PreservesReportExclusions( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); await sut.ExcludeAsync([cipher.Id], user.Id); @@ -165,7 +162,7 @@ public async Task ReplaceAsync_Cipher_PreservesReportExclusions( public async Task SoftDeleteAsync_KeepsReportExclusion( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { - var user = await CreateUserAsync(userRepository); + var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); await sut.ExcludeAsync([cipher.Id], user.Id); @@ -174,30 +171,17 @@ public async Task SoftDeleteAsync_KeepsReportExclusion( Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); } - [DatabaseTheory, DatabaseData] - public async Task DeleteAsync_RemovesExclusion( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) - { - var user = await CreateUserAsync(userRepository); - var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); - - await cipherRepository.DeleteAsync(cipher); - - Assert.DoesNotContain(await cipherRepository.GetManyByUserIdAsync(user.Id), c => c.Id == cipher.Id); - } - [DatabaseTheory, DatabaseData] public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { - var owner = await CreateUserAsync(userRepository); - var member = await CreateUserAsync(userRepository); - var org = await CreateOrganizationAsync(organizationRepository, owner); - var ownerOrgUser = await CreateOrgUserAsync(organizationUserRepository, org, owner, OrganizationUserType.Owner); - var memberOrgUser = await CreateOrgUserAsync(organizationUserRepository, org, member, OrganizationUserType.User); + var owner = await userRepository.CreateTestUserAsync(); + var member = await userRepository.CreateTestUserAsync(); + var org = await organizationRepository.CreateTestOrganizationAsync(); + var ownerOrgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(org, owner); + var memberOrgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(org, member); var collection = await collectionRepository.CreateAsync(new Collection { Name = "Shared", OrganizationId = org.Id }); await collectionRepository.UpdateUsersAsync(collection.Id, new List { @@ -213,35 +197,9 @@ public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ReportExcludedDate); Assert.Null((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ReportExcludedDate); } - - private static Task CreateUserAsync(IUserRepository userRepository) => - userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - private static Task CreateCipherAsync(ICipherRepository cipherRepository, Guid userId, CipherType type = CipherType.Login) => cipherRepository.CreateAsync(new Cipher { Type = type, UserId = userId, Data = "" }); - private static Task CreateOrganizationAsync(IOrganizationRepository organizationRepository, User user) => - organizationRepository.CreateAsync(new Organization - { - Name = "Test Organization", - BillingEmail = user.Email, - Plan = "Test", - }); - private static Task CreateOrgUserAsync(IOrganizationUserRepository organizationUserRepository, - Organization org, User user, OrganizationUserType type) => - organizationUserRepository.CreateAsync(new OrganizationUser - { - UserId = user.Id, - OrganizationId = org.Id, - Status = OrganizationUserStatusType.Confirmed, - Type = type, - }); } From e160edf375f47ba1b0b57723c054a1863e658363 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 14:27:59 -0400 Subject: [PATCH 08/27] [PM-41931] fix(dirt): Return report exclusion date in EF sync without organizations The EF GetManyByUserIdAsync branch for users without organizations rebuilds CipherDetails field by field and dropped ReportExcludedDate, so sync on Postgres, MySQL and SQLite returned null for users with no org. MSSQL was unaffected (its procedure selects every column). --- .../Vault/Repositories/CipherRepository.cs | 1 + .../CipherReportExclusionRepositoryTests.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 010261a7cffc..737757b282e2 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -518,6 +518,7 @@ public async Task> GetManyByUserIdAsync(Guid userId, OrganizationUseTotp = false, Key = c.Key, ArchivedDate = c.ArchivedDate, + ReportExcludedDate = c.ReportExcludedDate, }; } diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs index 9b63369a515a..09ac45df0065 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs @@ -129,6 +129,19 @@ public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( Assert.Null(ciphers.Single(c => c.Id == otherCipher.Id).ReportExcludedDate); } + [DatabaseTheory, DatabaseData] + public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsReportExcludedDate( + ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await userRepository.CreateTestUserAsync(); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + + await sut.ExcludeAsync([cipher.Id], user.Id); + + var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: false); + Assert.NotNull(ciphers.Single(c => c.Id == cipher.Id).ReportExcludedDate); + } + [DatabaseTheory, DatabaseData] public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) From 01125769fe392f47662280c1538821d0e20d0257 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 21 Oct 2026 15:01:01 -0400 Subject: [PATCH 09/27] [PM-41931] refactor(dirt): Store report exclusions as a per-user flag Store { "": true } instead of the exclusion date, matching the tech breakdown and the Favorites pattern. Nothing reads the date, and a flag tells the server less. Ciphers now expose a per-user ExcludedFromReports boolean in place of ReportExcludedDate. --- .../Models/Response/CipherResponseModel.cs | 4 +-- src/Core/Vault/Models/Data/CipherDetails.cs | 6 ++--- .../Dirt/CipherReportExclusionRepository.cs | 9 +++---- .../Queries/UserCipherDetailsQuery.cs | 22 +--------------- .../Vault/Repositories/CipherRepository.cs | 2 +- .../Queries/CipherDetailsQuery.cs | 4 +-- .../Cipher_ExcludeFromReports.sql | 2 +- src/Sql/dbo/Vault/Functions/CipherDetails.sql | 7 ++--- .../Cipher/CipherDetails_Create.sql | 2 +- .../CipherDetails_CreateWithCollections.sql | 2 +- .../Cipher/CipherDetails_ReadByIdUserId.sql | 4 +-- .../Cipher/CipherDetails_Update.sql | 2 +- .../CipherReportExclusionRepositoryTests.cs | 26 +++++++++---------- ...026-10-21_00_AddCipherReportExclusions.sql | 19 +++++++------- 14 files changed, 45 insertions(+), 66 deletions(-) diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index 8ab8e5fa4404..d737759f0b11 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -296,7 +296,7 @@ protected CipherResponseModel( Favorite = cipher.Favorite; Edit = cipher.Edit; ArchivedDate = cipher.ArchivedDate; - ReportExcludedDate = cipher.ReportExcludedDate; + ExcludedFromReports = cipher.ExcludedFromReports; ViewPassword = cipher.ViewPassword; Permissions = new CipherPermissionsResponseModel(user, cipher, organizationAbility); } @@ -313,7 +313,7 @@ public static CipherResponseModel From(FullCipherAccess access, CipherDetails ci public bool Edit { get; set; } public bool ViewPassword { get; set; } public DateTime? ArchivedDate { get; set; } - public DateTime? ReportExcludedDate { get; set; } + public bool ExcludedFromReports { get; set; } public CipherPermissionsResponseModel Permissions { get; set; } } diff --git a/src/Core/Vault/Models/Data/CipherDetails.cs b/src/Core/Vault/Models/Data/CipherDetails.cs index 49bb9dc1079e..124c9888549d 100644 --- a/src/Core/Vault/Models/Data/CipherDetails.cs +++ b/src/Core/Vault/Models/Data/CipherDetails.cs @@ -11,8 +11,8 @@ public class CipherDetails : CipherOrganizationDetails public bool Manage { get; set; } // Per-user archived date from Archives JSON. public DateTime? ArchivedDate { get; set; } - // Per-user health report exclusion date from ReportExclusions JSON. - public DateTime? ReportExcludedDate { get; set; } + // Per-user health report exclusion flag from ReportExclusions JSON. + public bool ExcludedFromReports { get; set; } public CipherDetails() { } public CipherDetails(CipherOrganizationDetails cipher) @@ -55,7 +55,7 @@ public CipherDetailsWithCollections( Key = cipher.Key; FolderId = cipher.FolderId; ArchivedDate = cipher.ArchivedDate; - ReportExcludedDate = cipher.ReportExcludedDate; + ExcludedFromReports = cipher.ExcludedFromReports; Favorite = cipher.Favorite; Edit = cipher.Edit; ViewPassword = cipher.ViewPassword; diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs index 272202e39d45..bf3393fa92fd 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs @@ -25,14 +25,13 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) && c.Type == CipherType.Login && c.DeletedDate == null) .ToListAsync(); - var utcNow = DateTime.UtcNow; foreach (var cipher in ciphers) { var exclusions = string.IsNullOrWhiteSpace(cipher.ReportExclusions) - ? new Dictionary() - : CoreHelpers.LoadClassFromJsonData>(cipher.ReportExclusions) - ?? new Dictionary(); - exclusions[userId] = utcNow; + ? new Dictionary() + : CoreHelpers.LoadClassFromJsonData>(cipher.ReportExclusions) + ?? new Dictionary(); + exclusions[userId] = true; cipher.ReportExclusions = CoreHelpers.ClassToJsonData(exclusions); } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index aeecd8e16d32..ef9ed692da15 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -120,7 +120,7 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }), - ReportExcludedDate = GetReportExcludedDate(_userId, new Cipher { Id = c.Id, ReportExclusions = c.ReportExclusions }) + ExcludedFromReports = _userId.HasValue && c.ReportExclusions != null && c.ReportExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true") }); return union; } @@ -146,26 +146,6 @@ from cg in cg_g.DefaultIfEmpty() } } - private static DateTime? GetReportExcludedDate(Guid? userId, Cipher cipher) - { - try - { - if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.ReportExclusions)) - { - var exclusions = JsonSerializer.Deserialize>(cipher.ReportExclusions); - if (exclusions.TryGetValue(userId.Value, out var excludedDate)) - { - return excludedDate; - } - } - - return null; - } - catch - { - return null; - } - } private static Guid? GetFolderId(Guid? userId, Cipher cipher) { diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 737757b282e2..21e2481bb3a2 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -518,7 +518,7 @@ public async Task> GetManyByUserIdAsync(Guid userId, OrganizationUseTotp = false, Key = c.Key, ArchivedDate = c.ArchivedDate, - ReportExcludedDate = c.ReportExcludedDate, + ExcludedFromReports = c.ExcludedFromReports, }; } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs index fa471cf5e72f..996cde102233 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs @@ -37,9 +37,7 @@ public virtual IQueryable Run(DatabaseContext dbContext) ArchivedDate = !_userId.HasValue || c.Archives == null || !c.Archives.ToLowerInvariant().Contains(_userId.Value.ToString()) ? null : CoreHelpers.LoadClassFromJsonData>(c.Archives)[_userId.Value], - ReportExcludedDate = !_userId.HasValue || c.ReportExclusions == null || !c.ReportExclusions.ToLowerInvariant().Contains(_userId.Value.ToString()) ? - null : - CoreHelpers.LoadClassFromJsonData>(c.ReportExclusions)[_userId.Value], + ExcludedFromReports = _userId.HasValue && c.ReportExclusions != null && c.ReportExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true"), }; return query; } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql index 372561b58931..f2a3984d66c2 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql @@ -11,7 +11,7 @@ BEGIN [ReportExclusions] = JSON_MODIFY( COALESCE(C.[ReportExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CONVERT(NVARCHAR(30), SYSUTCDATETIME(), 127) + CAST(1 AS BIT) ) FROM [dbo].[Cipher] C diff --git a/src/Sql/dbo/Vault/Functions/CipherDetails.sql b/src/Sql/dbo/Vault/Functions/CipherDetails.sql index f1b700889d3d..d1a30fd2648c 100644 --- a/src/Sql/dbo/Vault/Functions/CipherDetails.sql +++ b/src/Sql/dbo/Vault/Functions/CipherDetails.sql @@ -39,8 +39,9 @@ SELECT WHEN @UserId IS NULL OR C.[ReportExclusions] IS NULL - THEN NULL - ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"'))) - END [ReportExcludedDate] + OR JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"')) IS NULL + THEN 0 + ELSE 1 + END [ExcludedFromReports] FROM [dbo].[Cipher] C; diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql index 4100650cf366..8d3e2df0560d 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql index 4e02aeb870bf..cd2d4c37d376 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql index 4cfa32679187..e3383f407966 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql @@ -21,7 +21,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ReportExcludedDate], + [ExcludedFromReports], -- The highest rank holds Manage as 4, Edit as 2 and ViewPassword as 1; each flag is one part of it (MAX(R.[Rank]) / 2) % 2 AS [Edit], MAX(R.[Rank]) % 2 AS [ViewPassword], @@ -50,5 +50,5 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ReportExcludedDate] + [ExcludedFromReports] END diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index f6c57134418b..0b4f4332a319 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs index 09ac45df0065..e17fb5a38f00 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs @@ -13,7 +13,7 @@ namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; public class CipherReportExclusionRepositoryTests { [DatabaseTheory, DatabaseData] - public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersReportExcludedDate( + public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersExcludedFromReports( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -23,7 +23,7 @@ public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersReportExclude Assert.Equal(1, excluded); var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); - Assert.NotNull(details.ReportExcludedDate); + Assert.True(details.ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -74,7 +74,7 @@ public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( var cipher = await CreateCipherAsync(cipherRepository, owner.Id); Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id)); - Assert.Null((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ReportExcludedDate); + Assert.False((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -115,7 +115,7 @@ public async Task ExcludeAsync_BumpsAccountRevisionDate( } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( + public async Task GetManyByUserIdAsync_ReturnsExcludedFromReports( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -125,12 +125,12 @@ public async Task GetManyByUserIdAsync_ReturnsReportExcludedDate( await sut.ExcludeAsync([excludedCipher.Id], user.Id); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); - Assert.NotNull(ciphers.Single(c => c.Id == excludedCipher.Id).ReportExcludedDate); - Assert.Null(ciphers.Single(c => c.Id == otherCipher.Id).ReportExcludedDate); + Assert.True(ciphers.Single(c => c.Id == excludedCipher.Id).ExcludedFromReports); + Assert.False(ciphers.Single(c => c.Id == otherCipher.Id).ExcludedFromReports); } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsReportExcludedDate( + public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedFromReports( ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -139,7 +139,7 @@ public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsReportExclude await sut.ExcludeAsync([cipher.Id], user.Id); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: false); - Assert.NotNull(ciphers.Single(c => c.Id == cipher.Id).ReportExcludedDate); + Assert.True(ciphers.Single(c => c.Id == cipher.Id).ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -154,7 +154,7 @@ public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( details.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(details); - Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -168,7 +168,7 @@ public async Task ReplaceAsync_Cipher_PreservesReportExclusions( cipher.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(cipher); - Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -181,7 +181,7 @@ public async Task SoftDeleteAsync_KeepsReportExclusion( await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); - Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ReportExcludedDate); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); } [DatabaseTheory, DatabaseData] @@ -207,8 +207,8 @@ public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( cipher.OrganizationId = org.Id; await cipherRepository.ReplaceAsync(cipher, [collection.Id]); - Assert.NotNull((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ReportExcludedDate); - Assert.Null((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ReportExcludedDate); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromReports); + Assert.False((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ExcludedFromReports); } private static Task CreateCipherAsync(ICipherRepository cipherRepository, Guid userId, CipherType type = CipherType.Login) => diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql index cb1e0702127a..2f2c0af84cfa 100644 --- a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql @@ -46,9 +46,10 @@ SELECT WHEN @UserId IS NULL OR C.[ReportExclusions] IS NULL - THEN NULL - ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"'))) - END [ReportExcludedDate] + OR JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"')) IS NULL + THEN 0 + ELSE 1 + END [ExcludedFromReports] FROM [dbo].[Cipher] C; GO @@ -295,7 +296,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN @@ -370,7 +371,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS @@ -414,7 +415,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ReportExcludedDate DATETIME2(7) = NULL, -- not used + @ExcludedFromReports BIT = NULL, -- not used @ReportExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN @@ -500,7 +501,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ReportExcludedDate], + [ExcludedFromReports], MAX ([Edit]) AS [Edit], MAX ([ViewPassword]) AS [ViewPassword], MAX ([Manage]) AS [Manage] @@ -524,7 +525,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ReportExcludedDate] + [ExcludedFromReports] END GO @@ -541,7 +542,7 @@ BEGIN [ReportExclusions] = JSON_MODIFY( COALESCE(C.[ReportExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CONVERT(NVARCHAR(30), SYSUTCDATETIME(), 127) + CAST(1 AS BIT) ) FROM [dbo].[Cipher] C From 17ad7d9921f6df6a0488a81ca5fc4f7f1a1e6874 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 23 Oct 2026 11:14:49 -0400 Subject: [PATCH 10/27] [PM-41931] refactor(dirt): Move the exclude endpoint to its own PR This PR now holds only the database and data-access changes, so it can merge before the endpoint. The command, controller and their unit tests move to a stacked PR. --- .../CipherReportExclusionsController.cs | 33 ---------- .../ExcludeCipherFromReportsCommand.cs | 40 ------------- .../IExcludeCipherFromReportsCommand.cs | 7 --- .../ReportingServiceCollectionExtensions.cs | 1 - .../CipherReportExclusionsControllerTests.cs | 26 -------- .../ExcludeCipherFromReportsCommandTests.cs | 60 ------------------- 6 files changed, 167 deletions(-) delete mode 100644 src/Api/Dirt/Controllers/CipherReportExclusionsController.cs delete mode 100644 src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs delete mode 100644 src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs delete mode 100644 test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs delete mode 100644 test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs diff --git a/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs b/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs deleted file mode 100644 index 0a6bab2e3efb..000000000000 --- a/src/Api/Dirt/Controllers/CipherReportExclusionsController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Bit.Core; -using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; -using Bit.Core.Services; -using Bitwarden.Server.Sdk.Features; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Bit.Api.Dirt.Controllers; - -[Route("ciphers")] -[Authorize("Application")] -public class CipherReportExclusionsController : Controller -{ - private readonly IUserService _userService; - private readonly IExcludeCipherFromReportsCommand _excludeCipherFromReportsCommand; - - public CipherReportExclusionsController( - IUserService userService, - IExcludeCipherFromReportsCommand excludeCipherFromReportsCommand) - { - _userService = userService; - _excludeCipherFromReportsCommand = excludeCipherFromReportsCommand; - } - - /// Excludes the caller's personal login from their health reports. - [HttpPut("{id}/exclude-from-reports")] - [RequireFeature(FeatureFlagKeys.HealthReportExclusions)] - public async Task PutExcludeFromReportsAsync(Guid id) - { - var userId = _userService.GetProperUserId(User)!.Value; - await _excludeCipherFromReportsCommand.ExcludeAsync(id, userId); - } -} diff --git a/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs deleted file mode 100644 index 891f0f1a1c01..000000000000 --- a/src/Core/Dirt/Reports/ReportFeatures/ExcludeCipherFromReportsCommand.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Bit.Core.Billing.Premium.Queries; -using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; -using Bit.Core.Dirt.Repositories; -using Bit.Core.Exceptions; -using Bit.Core.Platform.Push; - -namespace Bit.Core.Dirt.Reports.ReportFeatures; - -public class ExcludeCipherFromReportsCommand : IExcludeCipherFromReportsCommand -{ - private readonly ICipherReportExclusionRepository _cipherReportExclusionRepository; - private readonly IHasPremiumAccessQuery _hasPremiumAccessQuery; - private readonly IPushNotificationService _pushService; - - public ExcludeCipherFromReportsCommand( - ICipherReportExclusionRepository cipherReportExclusionRepository, - IHasPremiumAccessQuery hasPremiumAccessQuery, - IPushNotificationService pushService) - { - _cipherReportExclusionRepository = cipherReportExclusionRepository; - _hasPremiumAccessQuery = hasPremiumAccessQuery; - _pushService = pushService; - } - - public async Task ExcludeAsync(Guid cipherId, Guid userId) - { - if (!await _hasPremiumAccessQuery.HasPremiumAccessAsync(userId)) - { - throw new BadRequestException("Premium required to exclude items from reports."); - } - - var excluded = await _cipherReportExclusionRepository.ExcludeAsync([cipherId], userId); - if (excluded == 0) - { - throw new NotFoundException(); - } - - await _pushService.PushSyncCiphersAsync(userId, true); - } -} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs deleted file mode 100644 index fb0d89921645..000000000000 --- a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IExcludeCipherFromReportsCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; - -public interface IExcludeCipherFromReportsCommand -{ - /// Excludes the user's personal login from their health reports. Requires premium. - Task ExcludeAsync(Guid cipherId, Guid userId); -} diff --git a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs index f2a5e02caf75..6aca3160daca 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs @@ -28,7 +28,6 @@ public static void AddReportingServices(this IServiceCollection services, IGloba services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); // v2 file storage commands services.AddScoped(); diff --git a/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs b/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs deleted file mode 100644 index b7004e157ee6..000000000000 --- a/test/Api.Test/Dirt/CipherReportExclusionsControllerTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Security.Claims; -using Bit.Api.Dirt.Controllers; -using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; -using Bit.Core.Services; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using NSubstitute; -using Xunit; - -namespace Bit.Api.Test.Dirt; - -[ControllerCustomize(typeof(CipherReportExclusionsController))] -[SutProviderCustomize] -public class CipherReportExclusionsControllerTests -{ - [Theory, BitAutoData] - public async Task PutExcludeFromReports_CallsCommandWithCallingUser( - SutProvider sutProvider, Guid cipherId, Guid userId) - { - sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(userId); - - await sutProvider.Sut.PutExcludeFromReportsAsync(cipherId); - - await sutProvider.GetDependency().Received(1).ExcludeAsync(cipherId, userId); - } -} diff --git a/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs deleted file mode 100644 index 4caceac150f8..000000000000 --- a/test/Core.Test/Dirt/ReportFeatures/ExcludeCipherFromReportsCommandTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Bit.Core.Billing.Premium.Queries; -using Bit.Core.Dirt.Reports.ReportFeatures; -using Bit.Core.Dirt.Repositories; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.Models; -using Bit.Core.Platform.Push; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using NSubstitute; -using Xunit; - -namespace Bit.Core.Test.Dirt.ReportFeatures; - -[SutProviderCustomize] -public class ExcludeCipherFromReportsCommandTests -{ - [Theory, BitAutoData] - public async Task ExcludeAsync_WithoutPremium_ThrowsBadRequest_AndDoesNotWrite( - SutProvider sutProvider, Guid cipherId, Guid userId) - { - sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(false); - - await Assert.ThrowsAsync(() => sutProvider.Sut.ExcludeAsync(cipherId, userId)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs().ExcludeAsync(default!, default); - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs().PushAsync(Arg.Any>()); - } - - [Theory, BitAutoData] - public async Task ExcludeAsync_NothingExcluded_ThrowsNotFound_AndDoesNotPush( - SutProvider sutProvider, Guid cipherId, Guid userId) - { - sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(true); - sutProvider.GetDependency() - .ExcludeAsync(Arg.Any>(), userId).Returns(0); - - await Assert.ThrowsAsync(() => sutProvider.Sut.ExcludeAsync(cipherId, userId)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs().PushAsync(Arg.Any>()); - } - - [Theory, BitAutoData] - public async Task ExcludeAsync_Excluded_PushesCipherSyncExcludingCurrentContext( - SutProvider sutProvider, Guid cipherId, Guid userId) - { - sutProvider.GetDependency().HasPremiumAccessAsync(userId).Returns(true); - sutProvider.GetDependency() - .ExcludeAsync(Arg.Is>(ids => ids.Single() == cipherId), userId).Returns(1); - - await sutProvider.Sut.ExcludeAsync(cipherId, userId); - - await sutProvider.GetDependency().Received(1) - .PushAsync(Arg.Is>(n => - n.Type == PushType.SyncCiphers && n.TargetId == userId && n.ExcludeCurrentContext)); - } -} From 36ec3257eae80f3f7bcd9de9e0bd7687ff7013e9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 23 Oct 2026 11:32:23 -0400 Subject: [PATCH 11/27] [PM-41931] chore(dirt): Re-date migrations after syncing with main Main gained newer migrations, so the SQL script moves to 2026-10-23 and the three EF migrations are regenerated on top of main's model. --- ...26-10-23_00_AddCipherReportExclusions.sql} | 0 ...949_AddCipherReportExclusions.Designer.cs} | 447 ++++++++++-------- ...261023152949_AddCipherReportExclusions.cs} | 0 ...935_AddCipherReportExclusions.Designer.cs} | 447 ++++++++++-------- ...261023152935_AddCipherReportExclusions.cs} | 0 ...942_AddCipherReportExclusions.Designer.cs} | 447 ++++++++++-------- ...261023152942_AddCipherReportExclusions.cs} | 0 7 files changed, 780 insertions(+), 561 deletions(-) rename util/Migrator/DbScripts/{2026-10-21_00_AddCipherReportExclusions.sql => 2026-10-23_00_AddCipherReportExclusions.sql} (100%) rename util/MySqlMigrations/Migrations/{20261021171939_AddCipherReportExclusions.Designer.cs => 20261023152949_AddCipherReportExclusions.Designer.cs} (98%) rename util/MySqlMigrations/Migrations/{20261021171939_AddCipherReportExclusions.cs => 20261023152949_AddCipherReportExclusions.cs} (100%) rename util/PostgresMigrations/Migrations/{20261021171844_AddCipherReportExclusions.Designer.cs => 20261023152935_AddCipherReportExclusions.Designer.cs} (98%) rename util/PostgresMigrations/Migrations/{20261021171844_AddCipherReportExclusions.cs => 20261023152935_AddCipherReportExclusions.cs} (100%) rename util/SqliteMigrations/Migrations/{20261021171852_AddCipherReportExclusions.Designer.cs => 20261023152942_AddCipherReportExclusions.Designer.cs} (98%) rename util/SqliteMigrations/Migrations/{20261021171852_AddCipherReportExclusions.cs => 20261023152942_AddCipherReportExclusions.cs} (100%) diff --git a/util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-23_00_AddCipherReportExclusions.sql similarity index 100% rename from util/Migrator/DbScripts/2026-10-21_00_AddCipherReportExclusions.sql rename to util/Migrator/DbScripts/2026-10-23_00_AddCipherReportExclusions.sql diff --git a/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs b/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs similarity index 98% rename from util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs rename to util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs index 54c8f45ffc57..446231c54b65 100644 --- a/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.Designer.cs +++ b/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.MySqlMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261021171939_AddCipherReportExclusions")] + [Migration("20261023152949_AddCipherReportExclusions")] partial class AddCipherReportExclusions { /// @@ -475,152 +475,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - 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.HasIndex("GatewayCustomerId"); - - b.HasIndex("GatewaySubscriptionId"); - - 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") @@ -868,6 +722,46 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("SsoUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeviceId") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Stamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "DeviceId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("TwoFactorRememberToken", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.Property("Id") @@ -1591,6 +1485,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("char(36)"); + b.Property("AccessLeaseId") + .HasColumnType("char(36)"); + + b.Property("AccessRequestId") + .HasColumnType("char(36)"); + b.Property("ActingUserId") .HasColumnType("char(36)"); @@ -2915,6 +2815,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TargetSystemId") .HasColumnType("char(36)"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + b.HasKey("Id"); b.HasIndex("CipherId") @@ -3011,6 +2915,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Status") .HasColumnType("tinyint unsigned"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + b.HasKey("Id"); b.HasIndex("OrganizationId"); @@ -3047,6 +2955,152 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Installation", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + 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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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.Providers.Models.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.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -3634,42 +3688,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") @@ -3738,6 +3756,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") @@ -3789,7 +3826,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -3800,7 +3837,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -3996,7 +4033,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("OrganizationId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade); @@ -4034,7 +4071,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .WithMany("Transactions") .HasForeignKey("OrganizationId"); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId"); @@ -4274,6 +4311,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.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.SecretsManager.Models.ApiKey", b => { b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") diff --git a/util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.cs b/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20261021171939_AddCipherReportExclusions.cs rename to util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.cs diff --git a/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs b/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs similarity index 98% rename from util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs rename to util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs index 0edb37a0211e..ab20d4e873b9 100644 --- a/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.Designer.cs +++ b/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.PostgresMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261021171844_AddCipherReportExclusions")] + [Migration("20261023152935_AddCipherReportExclusions")] partial class AddCipherReportExclusions { /// @@ -478,152 +478,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - 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.HasIndex("GatewayCustomerId"); - - b.HasIndex("GatewaySubscriptionId"); - - 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") @@ -873,6 +727,46 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("SsoUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceId") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Stamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "DeviceId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("TwoFactorRememberToken", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.Property("Id") @@ -1596,6 +1490,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("uuid"); + b.Property("AccessLeaseId") + .HasColumnType("uuid"); + + b.Property("AccessRequestId") + .HasColumnType("uuid"); + b.Property("ActingUserId") .HasColumnType("uuid"); @@ -2921,6 +2821,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TargetSystemId") .HasColumnType("uuid"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + b.HasKey("Id"); b.HasIndex("CipherId") @@ -3017,6 +2921,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Status") .HasColumnType("smallint"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + b.HasKey("Id"); b.HasIndex("OrganizationId"); @@ -3053,6 +2961,152 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Installation", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + 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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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.Providers.Models.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.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -3640,42 +3694,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") @@ -3744,6 +3762,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") @@ -3795,7 +3832,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -3806,7 +3843,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -4002,7 +4039,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("OrganizationId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade); @@ -4040,7 +4077,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .WithMany("Transactions") .HasForeignKey("OrganizationId"); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId"); @@ -4280,6 +4317,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.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.SecretsManager.Models.ApiKey", b => { b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") diff --git a/util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.cs b/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.cs similarity index 100% rename from util/PostgresMigrations/Migrations/20261021171844_AddCipherReportExclusions.cs rename to util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.cs diff --git a/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs b/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs similarity index 98% rename from util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs rename to util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs index 4f1613d3303a..9aa358e4160e 100644 --- a/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.Designer.cs +++ b/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs @@ -11,7 +11,7 @@ namespace Bit.SqliteMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261021171852_AddCipherReportExclusions")] + [Migration("20261023152942_AddCipherReportExclusions")] partial class AddCipherReportExclusions { /// @@ -470,152 +470,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .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.HasIndex("GatewayCustomerId"); - - b.HasIndex("GatewaySubscriptionId"); - - 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") @@ -857,6 +711,46 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("SsoUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Stamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "DeviceId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("TwoFactorRememberToken", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.Property("Id") @@ -1580,6 +1474,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("TEXT"); + b.Property("AccessLeaseId") + .HasColumnType("TEXT"); + + b.Property("AccessRequestId") + .HasColumnType("TEXT"); + b.Property("ActingUserId") .HasColumnType("TEXT"); @@ -2904,6 +2804,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TargetSystemId") .HasColumnType("TEXT"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("CipherId") @@ -3000,6 +2904,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Status") .HasColumnType("INTEGER"); + b.Property("WrappedKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("OrganizationId"); @@ -3036,6 +2944,152 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Installation", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .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.HasIndex("GatewayCustomerId"); + + b.HasIndex("GatewaySubscriptionId"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.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.Providers.Models.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.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -3623,42 +3677,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) 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") @@ -3727,6 +3745,25 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.TwoFactorRememberToken", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") @@ -3778,7 +3815,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -3789,7 +3826,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade) @@ -3985,7 +4022,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("OrganizationId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId") .OnDelete(DeleteBehavior.Cascade); @@ -4023,7 +4060,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .WithMany("Transactions") .HasForeignKey("OrganizationId"); - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") .WithMany() .HasForeignKey("ProviderId"); @@ -4263,6 +4300,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Providers.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Providers.Models.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.SecretsManager.Models.ApiKey", b => { b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") diff --git a/util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.cs b/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20261021171852_AddCipherReportExclusions.cs rename to util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.cs From 93dee7a86adf2d58354046f489ed89647cd1d028 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Oct 2026 15:06:15 -0400 Subject: [PATCH 12/27] [PM-41931] refactor(dirt): Rename report exclusions to browser health exclusions --- .../Models/Response/CipherResponseModel.cs | 4 +- ...CipherBrowserHealthExclusionRepository.cs} | 4 +- src/Core/Vault/Entities/Cipher.cs | 2 +- src/Core/Vault/Models/Data/CipherDetails.cs | 6 +- .../DapperServiceCollectionExtensions.cs | 2 +- ...CipherBrowserHealthExclusionRepository.cs} | 6 +- ...CipherBrowserHealthExclusionRepository.cs} | 10 +-- ...ityFrameworkServiceCollectionExtensions.cs | 2 +- .../Queries/UserCipherDetailsQuery.cs | 6 +- .../Vault/Repositories/CipherRepository.cs | 6 +- .../Queries/CipherDetailsQuery.cs | 2 +- ...ql => Cipher_ExcludeFromBrowserHealth.sql} | 6 +- src/Sql/dbo/Vault/Functions/CipherDetails.sql | 6 +- .../Cipher/CipherDetails_Create.sql | 4 +- .../CipherDetails_CreateWithCollections.sql | 4 +- .../Cipher/CipherDetails_ReadByIdUserId.sql | 4 +- .../Cipher/CipherDetails_Update.sql | 4 +- .../Cipher/Cipher_Create.sql | 2 +- .../Cipher/Cipher_CreateWithCollections.sql | 2 +- .../Cipher/Cipher_Update.sql | 2 +- .../Cipher/Cipher_UpdateWithCollections.sql | 2 +- src/Sql/dbo/Vault/Tables/Cipher.sql | 2 +- ...rBrowserHealthExclusionRepositoryTests.cs} | 62 +++++++++---------- ...6_00_AddCipherBrowserHealthExclusions.sql} | 40 ++++++------ ...CipherBrowserHealthExclusions.Designer.cs} | 10 +-- ...90333_AddCipherBrowserHealthExclusions.cs} | 6 +- .../DatabaseContextModelSnapshot.cs | 6 +- ...CipherBrowserHealthExclusions.Designer.cs} | 10 +-- ...90223_AddCipherBrowserHealthExclusions.cs} | 6 +- .../DatabaseContextModelSnapshot.cs | 6 +- ...CipherBrowserHealthExclusions.Designer.cs} | 10 +-- ...90237_AddCipherBrowserHealthExclusions.cs} | 6 +- .../DatabaseContextModelSnapshot.cs | 6 +- 33 files changed, 128 insertions(+), 128 deletions(-) rename src/Core/Dirt/Repositories/{ICipherReportExclusionRepository.cs => ICipherBrowserHealthExclusionRepository.cs} (59%) rename src/Infrastructure.Dapper/Dirt/{CipherReportExclusionRepository.cs => CipherBrowserHealthExclusionRepository.cs} (73%) rename src/Infrastructure.EntityFramework/Dirt/{CipherReportExclusionRepository.cs => CipherBrowserHealthExclusionRepository.cs} (74%) rename src/Sql/dbo/Dirt/Stored Procedures/{Cipher_ExcludeFromReports.sql => Cipher_ExcludeFromBrowserHealth.sql} (78%) rename test/Infrastructure.IntegrationTest/Dirt/Repositories/{CipherReportExclusionRepositoryTests.cs => CipherBrowserHealthExclusionRepositoryTests.cs} (75%) rename util/Migrator/DbScripts/{2026-10-23_00_AddCipherReportExclusions.sql => 2026-10-26_00_AddCipherBrowserHealthExclusions.sql} (92%) rename util/MySqlMigrations/Migrations/{20261023152949_AddCipherReportExclusions.Designer.cs => 20261026190333_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/MySqlMigrations/Migrations/{20261023152949_AddCipherReportExclusions.cs => 20261026190333_AddCipherBrowserHealthExclusions.cs} (79%) rename util/PostgresMigrations/Migrations/{20261023152935_AddCipherReportExclusions.Designer.cs => 20261026190223_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/PostgresMigrations/Migrations/{20261023152935_AddCipherReportExclusions.cs => 20261026190223_AddCipherBrowserHealthExclusions.cs} (77%) rename util/SqliteMigrations/Migrations/{20261023152942_AddCipherReportExclusions.Designer.cs => 20261026190237_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/SqliteMigrations/Migrations/{20261023152942_AddCipherReportExclusions.cs => 20261026190237_AddCipherBrowserHealthExclusions.cs} (77%) diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index d737759f0b11..4ad2acbacd1f 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -296,7 +296,7 @@ protected CipherResponseModel( Favorite = cipher.Favorite; Edit = cipher.Edit; ArchivedDate = cipher.ArchivedDate; - ExcludedFromReports = cipher.ExcludedFromReports; + ExcludedFromBrowserHealth = cipher.ExcludedFromBrowserHealth; ViewPassword = cipher.ViewPassword; Permissions = new CipherPermissionsResponseModel(user, cipher, organizationAbility); } @@ -313,7 +313,7 @@ public static CipherResponseModel From(FullCipherAccess access, CipherDetails ci public bool Edit { get; set; } public bool ViewPassword { get; set; } public DateTime? ArchivedDate { get; set; } - public bool ExcludedFromReports { get; set; } + public bool ExcludedFromBrowserHealth { get; set; } public CipherPermissionsResponseModel Permissions { get; set; } } diff --git a/src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs similarity index 59% rename from src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs rename to src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs index ac17ede1b67b..16864beb7e4e 100644 --- a/src/Core/Dirt/Repositories/ICipherReportExclusionRepository.cs +++ b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs @@ -1,7 +1,7 @@ namespace Bit.Core.Dirt.Repositories; -public interface ICipherReportExclusionRepository +public interface ICipherBrowserHealthExclusionRepository { - /// Excludes the user's personal, not-deleted logins from health reports. Returns how many were excluded. + /// Excludes the user's personal, not-deleted logins from browser health. Returns how many were excluded. Task ExcludeAsync(IEnumerable cipherIds, Guid userId); } diff --git a/src/Core/Vault/Entities/Cipher.cs b/src/Core/Vault/Entities/Cipher.cs index fdc68ede867e..59d4df6d5b7f 100644 --- a/src/Core/Vault/Entities/Cipher.cs +++ b/src/Core/Vault/Entities/Cipher.cs @@ -27,7 +27,7 @@ public class Cipher : ITableObject, ICloneable public Enums.CipherRepromptType? Reprompt { get; set; } public string Key { get; set; } public string Archives { get; set; } - public string ReportExclusions { get; set; } + public string BrowserHealthExclusions { get; set; } public void SetNewId() { diff --git a/src/Core/Vault/Models/Data/CipherDetails.cs b/src/Core/Vault/Models/Data/CipherDetails.cs index 124c9888549d..ffdf09e53186 100644 --- a/src/Core/Vault/Models/Data/CipherDetails.cs +++ b/src/Core/Vault/Models/Data/CipherDetails.cs @@ -11,8 +11,8 @@ public class CipherDetails : CipherOrganizationDetails public bool Manage { get; set; } // Per-user archived date from Archives JSON. public DateTime? ArchivedDate { get; set; } - // Per-user health report exclusion flag from ReportExclusions JSON. - public bool ExcludedFromReports { get; set; } + // Per-user browser health exclusion flag from BrowserHealthExclusions JSON. + public bool ExcludedFromBrowserHealth { get; set; } public CipherDetails() { } public CipherDetails(CipherOrganizationDetails cipher) @@ -55,7 +55,7 @@ public CipherDetailsWithCollections( Key = cipher.Key; FolderId = cipher.FolderId; ArchivedDate = cipher.ArchivedDate; - ExcludedFromReports = cipher.ExcludedFromReports; + ExcludedFromBrowserHealth = cipher.ExcludedFromBrowserHealth; Favorite = cipher.Favorite; Edit = cipher.Edit; ViewPassword = cipher.ViewPassword; diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 9912bc6ca8b9..cd3e5786e2ef 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -96,7 +96,7 @@ public static void AddDapperRepositories(this IServiceCollection services, bool services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs similarity index 73% rename from src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs rename to src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index 5214c0dbfa66..3856bedca8d2 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherReportExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -7,9 +7,9 @@ namespace Bit.Infrastructure.Dapper.Dirt; -public class CipherReportExclusionRepository : BaseRepository, ICipherReportExclusionRepository +public class CipherBrowserHealthExclusionRepository : BaseRepository, ICipherBrowserHealthExclusionRepository { - public CipherReportExclusionRepository(GlobalSettings globalSettings) + public CipherBrowserHealthExclusionRepository(GlobalSettings globalSettings) : base(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) { } @@ -18,7 +18,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) { await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( - "[dbo].[Cipher_ExcludeFromReports]", + "[dbo].[Cipher_ExcludeFromBrowserHealth]", new { Ids = cipherIds.ToGuidIdArrayTVP(), UserId = userId }, commandType: CommandType.StoredProcedure); } diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs similarity index 74% rename from src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs rename to src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs index bf3393fa92fd..7c4553c61033 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherReportExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -8,9 +8,9 @@ namespace Bit.Infrastructure.EntityFramework.Dirt; -public class CipherReportExclusionRepository : BaseEntityFrameworkRepository, ICipherReportExclusionRepository +public class CipherBrowserHealthExclusionRepository : BaseEntityFrameworkRepository, ICipherBrowserHealthExclusionRepository { - public CipherReportExclusionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + public CipherBrowserHealthExclusionRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper) { } @@ -27,12 +27,12 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) foreach (var cipher in ciphers) { - var exclusions = string.IsNullOrWhiteSpace(cipher.ReportExclusions) + var exclusions = string.IsNullOrWhiteSpace(cipher.BrowserHealthExclusions) ? new Dictionary() - : CoreHelpers.LoadClassFromJsonData>(cipher.ReportExclusions) + : CoreHelpers.LoadClassFromJsonData>(cipher.BrowserHealthExclusions) ?? new Dictionary(); exclusions[userId] = true; - cipher.ReportExclusions = CoreHelpers.ClassToJsonData(exclusions); + cipher.BrowserHealthExclusions = CoreHelpers.ClassToJsonData(exclusions); } if (ciphers.Count > 0) diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 2fc87c16cbca..72f1b1ae823e 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -132,7 +132,7 @@ public static void AddPasswordManagerEFRepositories(this IServiceCollection serv services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); if (selfHosted) diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index ef9ed692da15..a5fa6b097470 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -72,7 +72,7 @@ from cg in cg_g.DefaultIfEmpty() c.Reprompt, c.Key, c.Archives, - c.ReportExclusions + c.BrowserHealthExclusions }; var query2 = from c in dbContext.Ciphers @@ -97,7 +97,7 @@ from cg in cg_g.DefaultIfEmpty() c.Reprompt, c.Key, c.Archives, - c.ReportExclusions + c.BrowserHealthExclusions }; var union = query.Union(query2).Select(c => new CipherDetails @@ -120,7 +120,7 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }), - ExcludedFromReports = _userId.HasValue && c.ReportExclusions != null && c.ReportExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true") + ExcludedFromBrowserHealth = _userId.HasValue && c.BrowserHealthExclusions != null && c.BrowserHealthExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true") }); return union; } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 21e2481bb3a2..5026b0a30d17 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -518,7 +518,7 @@ public async Task> GetManyByUserIdAsync(Guid userId, OrganizationUseTotp = false, Key = c.Key, ArchivedDate = c.ArchivedDate, - ExcludedFromReports = c.ExcludedFromReports, + ExcludedFromBrowserHealth = c.ExcludedFromBrowserHealth, }; } @@ -585,7 +585,7 @@ public override async Task ReplaceAsync(Core.Vault.Entities.Cipher cipher) { var mappedEntity = Mapper.Map(cipher); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); - dbContext.Entry(entity).Property(c => c.ReportExclusions).IsModified = false; + dbContext.Entry(entity).Property(c => c.BrowserHealthExclusions).IsModified = false; await dbContext.SaveChangesAsync(); } } @@ -664,7 +664,7 @@ public async Task ReplaceAsync(CipherDetails cipher) var mappedEntity = Mapper.Map(cipher); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); - dbContext.Entry(entity).Property(c => c.ReportExclusions).IsModified = false; + dbContext.Entry(entity).Property(c => c.BrowserHealthExclusions).IsModified = false; if (cipher.OrganizationId.HasValue) { diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs index 996cde102233..1d53ac43458b 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs @@ -37,7 +37,7 @@ public virtual IQueryable Run(DatabaseContext dbContext) ArchivedDate = !_userId.HasValue || c.Archives == null || !c.Archives.ToLowerInvariant().Contains(_userId.Value.ToString()) ? null : CoreHelpers.LoadClassFromJsonData>(c.Archives)[_userId.Value], - ExcludedFromReports = _userId.HasValue && c.ReportExclusions != null && c.ReportExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true"), + ExcludedFromBrowserHealth = _userId.HasValue && c.BrowserHealthExclusions != null && c.BrowserHealthExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true"), }; return query; } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql similarity index 78% rename from src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql rename to src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql index f2a3984d66c2..4b08d6a6b997 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromReports.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[Cipher_ExcludeFromReports] +CREATE PROCEDURE [dbo].[Cipher_ExcludeFromBrowserHealth] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER AS @@ -8,8 +8,8 @@ BEGIN UPDATE C SET - [ReportExclusions] = JSON_MODIFY( - COALESCE(C.[ReportExclusions], N'{}'), + [BrowserHealthExclusions] = JSON_MODIFY( + COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), CAST(1 AS BIT) ) diff --git a/src/Sql/dbo/Vault/Functions/CipherDetails.sql b/src/Sql/dbo/Vault/Functions/CipherDetails.sql index d1a30fd2648c..a694b21c27f4 100644 --- a/src/Sql/dbo/Vault/Functions/CipherDetails.sql +++ b/src/Sql/dbo/Vault/Functions/CipherDetails.sql @@ -38,10 +38,10 @@ SELECT CASE WHEN @UserId IS NULL - OR C.[ReportExclusions] IS NULL - OR JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"')) IS NULL + OR C.[BrowserHealthExclusions] IS NULL + OR JSON_VALUE(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN 0 ELSE 1 - END [ExcludedFromReports] + END [ExcludedFromBrowserHealth] FROM [dbo].[Cipher] C; diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql index 8d3e2df0560d..da9378328376 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql @@ -20,8 +20,8 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql index cd2d4c37d376..450d5cd152d4 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql @@ -20,8 +20,8 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql index e3383f407966..7c44a1f8068f 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql @@ -21,7 +21,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromReports], + [ExcludedFromBrowserHealth], -- The highest rank holds Manage as 4, Edit as 2 and ViewPassword as 1; each flag is one part of it (MAX(R.[Rank]) / 2) % 2 AS [Edit], MAX(R.[Rank]) % 2 AS [ViewPassword], @@ -50,5 +50,5 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromReports] + [ExcludedFromBrowserHealth] END diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index 0b4f4332a319..52ca63a90800 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -20,8 +20,8 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql index ce3c00b3a9de..7d7c85b4babc 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql index 6b465649b2f1..79289d1afc32 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql index b4764b11dd9d..90ace7ac0951 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql index 972159674393..e710796e13c9 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Tables/Cipher.sql b/src/Sql/dbo/Vault/Tables/Cipher.sql index f02257705438..42749bb241da 100644 --- a/src/Sql/dbo/Vault/Tables/Cipher.sql +++ b/src/Sql/dbo/Vault/Tables/Cipher.sql @@ -15,7 +15,7 @@ CREATE TABLE [dbo].[Cipher] ( [Key] VARCHAR(MAX) NULL, [ArchivedDate] DATETIME2 (7) NULL, [Archives] NVARCHAR(MAX) NULL, - [ReportExclusions] NVARCHAR(MAX) NULL, + [BrowserHealthExclusions] NVARCHAR(MAX) NULL, CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs similarity index 75% rename from test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs rename to test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index e17fb5a38f00..8015856ddf32 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherReportExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -10,11 +10,11 @@ namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; -public class CipherReportExclusionRepositoryTests +public class CipherBrowserHealthExclusionRepositoryTests { [DatabaseTheory, DatabaseData] - public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersExcludedFromReports( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersExcludedFromBrowserHealth( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -23,12 +23,12 @@ public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersExcludedFromR Assert.Equal(1, excluded); var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); - Assert.True(details.ExcludedFromReports); + Assert.True(details.ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, IOrganizationRepository organizationRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -46,7 +46,7 @@ public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id, CipherType.SecureNote); @@ -56,7 +56,7 @@ public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_DeletedCipher_ReturnsZero( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -67,19 +67,19 @@ public async Task ExcludeAsync_DeletedCipher_ReturnsZero( [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var owner = await userRepository.CreateTestUserAsync(); var otherUser = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, owner.Id); Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id)); - Assert.False((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromReports); + Assert.False((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_AlreadyExcluded_ReturnsOne( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -90,7 +90,7 @@ public async Task ExcludeAsync_AlreadyExcluded_ReturnsOne( [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -103,7 +103,7 @@ public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_BumpsAccountRevisionDate( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -115,8 +115,8 @@ public async Task ExcludeAsync_BumpsAccountRevisionDate( } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_ReturnsExcludedFromReports( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task GetManyByUserIdAsync_ReturnsExcludedFromBrowserHealth( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var excludedCipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -125,13 +125,13 @@ public async Task GetManyByUserIdAsync_ReturnsExcludedFromReports( await sut.ExcludeAsync([excludedCipher.Id], user.Id); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); - Assert.True(ciphers.Single(c => c.Id == excludedCipher.Id).ExcludedFromReports); - Assert.False(ciphers.Single(c => c.Id == otherCipher.Id).ExcludedFromReports); + Assert.True(ciphers.Single(c => c.Id == excludedCipher.Id).ExcludedFromBrowserHealth); + Assert.False(ciphers.Single(c => c.Id == otherCipher.Id).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedFromReports( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedFromBrowserHealth( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -139,12 +139,12 @@ public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedFromR await sut.ExcludeAsync([cipher.Id], user.Id); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: false); - Assert.True(ciphers.Single(c => c.Id == cipher.Id).ExcludedFromReports); + Assert.True(ciphers.Single(c => c.Id == cipher.Id).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] - public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task ReplaceAsync_CipherDetails_PreservesBrowserHealthExclusions( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -154,12 +154,12 @@ public async Task ReplaceAsync_CipherDetails_PreservesReportExclusions( details.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(details); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] - public async Task ReplaceAsync_Cipher_PreservesReportExclusions( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task ReplaceAsync_Cipher_PreservesBrowserHealthExclusions( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -168,12 +168,12 @@ public async Task ReplaceAsync_Cipher_PreservesReportExclusions( cipher.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(cipher); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] - public async Task SoftDeleteAsync_KeepsReportExclusion( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + public async Task SoftDeleteAsync_KeepsBrowserHealthExclusion( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); @@ -181,12 +181,12 @@ public async Task SoftDeleteAsync_KeepsReportExclusion( await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromReports); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); } [DatabaseTheory, DatabaseData] public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( - ICipherReportExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { @@ -207,8 +207,8 @@ public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( cipher.OrganizationId = org.Id; await cipherRepository.ReplaceAsync(cipher, [collection.Id]); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromReports); - Assert.False((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ExcludedFromReports); + Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromBrowserHealth); + Assert.False((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ExcludedFromBrowserHealth); } private static Task CreateCipherAsync(ICipherRepository cipherRepository, Guid userId, CipherType type = CipherType.Login) => diff --git a/util/Migrator/DbScripts/2026-10-23_00_AddCipherReportExclusions.sql b/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql similarity index 92% rename from util/Migrator/DbScripts/2026-10-23_00_AddCipherReportExclusions.sql rename to util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql index 2f2c0af84cfa..4e5695d3cbdd 100644 --- a/util/Migrator/DbScripts/2026-10-23_00_AddCipherReportExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql @@ -1,7 +1,7 @@ -IF COL_LENGTH('[dbo].[Cipher]', 'ReportExclusions') IS NULL +IF COL_LENGTH('[dbo].[Cipher]', 'BrowserHealthExclusions') IS NULL BEGIN ALTER TABLE [dbo].[Cipher] - ADD [ReportExclusions] NVARCHAR(MAX) NULL; + ADD [BrowserHealthExclusions] NVARCHAR(MAX) NULL; END GO @@ -45,11 +45,11 @@ SELECT CASE WHEN @UserId IS NULL - OR C.[ReportExclusions] IS NULL - OR JSON_VALUE(C.[ReportExclusions], CONCAT('$."', @UserId, '"')) IS NULL + OR C.[BrowserHealthExclusions] IS NULL + OR JSON_VALUE(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN 0 ELSE 1 - END [ExcludedFromReports] + END [ExcludedFromBrowserHealth] FROM [dbo].[Cipher] C; GO @@ -75,7 +75,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -139,7 +139,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -189,7 +189,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_CreateWithCollections] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -224,7 +224,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateWithCollections] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -296,8 +296,8 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -371,8 +371,8 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL, -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -415,8 +415,8 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromReports BIT = NULL, -- not used - @ReportExclusions NVARCHAR(MAX) = NULL -- not used + @ExcludedFromBrowserHealth BIT = NULL, -- not used + @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -501,7 +501,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromReports], + [ExcludedFromBrowserHealth], MAX ([Edit]) AS [Edit], MAX ([ViewPassword]) AS [ViewPassword], MAX ([Manage]) AS [Manage] @@ -525,11 +525,11 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromReports] + [ExcludedFromBrowserHealth] END GO -CREATE OR ALTER PROCEDURE [dbo].[Cipher_ExcludeFromReports] +CREATE OR ALTER PROCEDURE [dbo].[Cipher_ExcludeFromBrowserHealth] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER AS @@ -539,8 +539,8 @@ BEGIN UPDATE C SET - [ReportExclusions] = JSON_MODIFY( - COALESCE(C.[ReportExclusions], N'{}'), + [BrowserHealthExclusions] = JSON_MODIFY( + COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), CAST(1 AS BIT) ) diff --git a/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs b/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs rename to util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs index 446231c54b65..69f7eaee48c7 100644 --- a/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.Designer.cs +++ b/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs @@ -12,8 +12,8 @@ namespace Bit.MySqlMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261023152949_AddCipherReportExclusions")] - partial class AddCipherReportExclusions + [Migration("20261026190333_AddCipherBrowserHealthExclusions")] + partial class AddCipherBrowserHealthExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3325,6 +3325,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("longtext"); + b.Property("BrowserHealthExclusions") + .HasColumnType("longtext"); + b.Property("CreationDate") .HasColumnType("datetime(6)"); @@ -3346,9 +3349,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("char(36)"); - b.Property("ReportExclusions") - .HasColumnType("longtext"); - b.Property("Reprompt") .HasColumnType("tinyint unsigned"); diff --git a/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.cs b/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.cs similarity index 79% rename from util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.cs rename to util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.cs index db2b091004b7..c1cf1f0c095a 100644 --- a/util/MySqlMigrations/Migrations/20261023152949_AddCipherReportExclusions.cs +++ b/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.MySqlMigrations.Migrations; /// -public partial class AddCipherReportExclusions : Migration +public partial class AddCipherBrowserHealthExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher", type: "longtext", nullable: true) @@ -22,7 +22,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher"); } } diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 2bba2f0967aa..494983089eae 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3328,6 +3328,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("longtext"); + b.Property("BrowserHealthExclusions") + .HasColumnType("longtext"); + b.Property("CreationDate") .HasColumnType("datetime(6)"); @@ -3349,9 +3352,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("char(36)"); - b.Property("ReportExclusions") - .HasColumnType("longtext"); - b.Property("Reprompt") .HasColumnType("tinyint unsigned"); diff --git a/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs b/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs rename to util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs index ab20d4e873b9..7f09b535bdd5 100644 --- a/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.Designer.cs +++ b/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs @@ -12,8 +12,8 @@ namespace Bit.PostgresMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261023152935_AddCipherReportExclusions")] - partial class AddCipherReportExclusions + [Migration("20261026190223_AddCipherBrowserHealthExclusions")] + partial class AddCipherBrowserHealthExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3331,6 +3331,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("text"); + b.Property("BrowserHealthExclusions") + .HasColumnType("text"); + b.Property("CreationDate") .HasColumnType("timestamp with time zone"); @@ -3352,9 +3355,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("uuid"); - b.Property("ReportExclusions") - .HasColumnType("text"); - b.Property("Reprompt") .HasColumnType("smallint"); diff --git a/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.cs b/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.cs similarity index 77% rename from util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.cs rename to util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.cs index 6b279b80e2ae..d82fc9e41fd3 100644 --- a/util/PostgresMigrations/Migrations/20261023152935_AddCipherReportExclusions.cs +++ b/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.PostgresMigrations.Migrations; /// -public partial class AddCipherReportExclusions : Migration +public partial class AddCipherBrowserHealthExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher", type: "text", nullable: true); @@ -21,7 +21,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher"); } } diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 4f333304b1df..7f0918973903 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3334,6 +3334,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("text"); + b.Property("BrowserHealthExclusions") + .HasColumnType("text"); + b.Property("CreationDate") .HasColumnType("timestamp with time zone"); @@ -3355,9 +3358,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("uuid"); - b.Property("ReportExclusions") - .HasColumnType("text"); - b.Property("Reprompt") .HasColumnType("smallint"); diff --git a/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs b/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs rename to util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs index 9aa358e4160e..1ac946a01a3a 100644 --- a/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.Designer.cs +++ b/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs @@ -11,8 +11,8 @@ namespace Bit.SqliteMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261023152942_AddCipherReportExclusions")] - partial class AddCipherReportExclusions + [Migration("20261026190237_AddCipherBrowserHealthExclusions")] + partial class AddCipherBrowserHealthExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3314,6 +3314,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("TEXT"); + b.Property("BrowserHealthExclusions") + .HasColumnType("TEXT"); + b.Property("CreationDate") .HasColumnType("TEXT"); @@ -3335,9 +3338,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("TEXT"); - b.Property("ReportExclusions") - .HasColumnType("TEXT"); - b.Property("Reprompt") .HasColumnType("INTEGER"); diff --git a/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.cs b/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.cs similarity index 77% rename from util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.cs rename to util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.cs index 8b40abb161af..c1a6cf33fa01 100644 --- a/util/SqliteMigrations/Migrations/20261023152942_AddCipherReportExclusions.cs +++ b/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.SqliteMigrations.Migrations; /// -public partial class AddCipherReportExclusions : Migration +public partial class AddCipherBrowserHealthExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher", type: "TEXT", nullable: true); @@ -21,7 +21,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "ReportExclusions", + name: "BrowserHealthExclusions", table: "Cipher"); } } diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index e878eafbe413..135b97a3e72b 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3317,6 +3317,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("TEXT"); + b.Property("BrowserHealthExclusions") + .HasColumnType("TEXT"); + b.Property("CreationDate") .HasColumnType("TEXT"); @@ -3338,9 +3341,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("TEXT"); - b.Property("ReportExclusions") - .HasColumnType("TEXT"); - b.Property("Reprompt") .HasColumnType("INTEGER"); From fdf9e4913f06e0032f063359659b7f87b21b7e42 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Oct 2026 16:35:48 -0400 Subject: [PATCH 13/27] [PM-41931] refactor(dirt): Rename the exclusion procedure to UpdateMany Follow the SQL naming guide: Cipher_UpdateManyBrowserHealthExclusions. --- .../Dirt/CipherBrowserHealthExclusionRepository.cs | 2 +- ...rHealth.sql => Cipher_UpdateManyBrowserHealthExclusions.sql} | 2 +- .../2026-10-26_00_AddCipherBrowserHealthExclusions.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Sql/dbo/Dirt/Stored Procedures/{Cipher_ExcludeFromBrowserHealth.sql => Cipher_UpdateManyBrowserHealthExclusions.sql} (91%) diff --git a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index 3856bedca8d2..d2f442b74df3 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -18,7 +18,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) { await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( - "[dbo].[Cipher_ExcludeFromBrowserHealth]", + "[dbo].[Cipher_UpdateManyBrowserHealthExclusions]", new { Ids = cipherIds.ToGuidIdArrayTVP(), UserId = userId }, commandType: CommandType.StoredProcedure); } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql similarity index 91% rename from src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql rename to src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql index 4b08d6a6b997..0c32e3736432 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_ExcludeFromBrowserHealth.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[Cipher_ExcludeFromBrowserHealth] +CREATE PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER AS diff --git a/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql index 4e5695d3cbdd..dcc82c5335d3 100644 --- a/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql @@ -529,7 +529,7 @@ SELECT END GO -CREATE OR ALTER PROCEDURE [dbo].[Cipher_ExcludeFromBrowserHealth] +CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER AS From 26f470e191ef798017910ac9504710d1c50d3335 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Oct 2026 16:58:25 -0400 Subject: [PATCH 14/27] [PM-41931] style(dirt): Align the column types in Cipher.sql --- src/Sql/dbo/Vault/Tables/Cipher.sql | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Sql/dbo/Vault/Tables/Cipher.sql b/src/Sql/dbo/Vault/Tables/Cipher.sql index 42749bb241da..37d42838379e 100644 --- a/src/Sql/dbo/Vault/Tables/Cipher.sql +++ b/src/Sql/dbo/Vault/Tables/Cipher.sql @@ -1,21 +1,21 @@  CREATE TABLE [dbo].[Cipher] ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [UserId] UNIQUEIDENTIFIER NULL, - [OrganizationId] UNIQUEIDENTIFIER NULL, - [Type] TINYINT NOT NULL, - [Data] NVARCHAR (MAX) NOT NULL, - [Favorites] NVARCHAR (MAX) NULL, - [Folders] NVARCHAR (MAX) NULL, - [Attachments] NVARCHAR (MAX) NULL, - [CreationDate] DATETIME2 (7) NOT NULL, - [RevisionDate] DATETIME2 (7) NOT NULL, - [DeletedDate] DATETIME2 (7) NULL, - [Reprompt] TINYINT NULL, - [Key] VARCHAR(MAX) NULL, - [ArchivedDate] DATETIME2 (7) NULL, - [Archives] NVARCHAR(MAX) NULL, - [BrowserHealthExclusions] NVARCHAR(MAX) NULL, + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NULL, + [OrganizationId] UNIQUEIDENTIFIER NULL, + [Type] TINYINT NOT NULL, + [Data] NVARCHAR (MAX) NOT NULL, + [Favorites] NVARCHAR (MAX) NULL, + [Folders] NVARCHAR (MAX) NULL, + [Attachments] NVARCHAR (MAX) NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + [DeletedDate] DATETIME2 (7) NULL, + [Reprompt] TINYINT NULL, + [Key] VARCHAR(MAX) NULL, + [ArchivedDate] DATETIME2 (7) NULL, + [Archives] NVARCHAR(MAX) NULL, + [BrowserHealthExclusions] NVARCHAR(MAX) NULL, CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) From 26496f64e26d3405219ccf265cfe097ef36bc1d7 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Oct 2026 17:03:29 -0400 Subject: [PATCH 15/27] [PM-41931] refactor(dirt): Return early when no cipher ids are passed --- .../Dirt/CipherBrowserHealthExclusionRepository.cs | 8 +++++++- .../Dirt/CipherBrowserHealthExclusionRepository.cs | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index d2f442b74df3..4f16c768abce 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -16,10 +16,16 @@ public CipherBrowserHealthExclusionRepository(GlobalSettings globalSettings) public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) { + var ids = cipherIds.ToList(); + if (ids.Count == 0) + { + return 0; + } + await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( "[dbo].[Cipher_UpdateManyBrowserHealthExclusions]", - new { Ids = cipherIds.ToGuidIdArrayTVP(), UserId = userId }, + new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId }, commandType: CommandType.StoredProcedure); } } diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs index 7c4553c61033..bc3e07ee9100 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -17,9 +17,14 @@ public CipherBrowserHealthExclusionRepository(IServiceScopeFactory serviceScopeF public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) { + var ids = cipherIds.ToList(); + if (ids.Count == 0) + { + return 0; + } + await using var scope = ServiceScopeFactory.CreateAsyncScope(); var dbContext = GetDatabaseContext(scope); - var ids = cipherIds.ToList(); var ciphers = await dbContext.Ciphers .Where(c => ids.Contains(c.Id) && c.UserId == userId && c.OrganizationId == null && c.Type == CipherType.Login && c.DeletedDate == null) From 478d9a149a6d52da6e59f27f8c1361bb3e7b6b9f Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 26 Oct 2026 17:55:22 -0400 Subject: [PATCH 16/27] [PM-41931] chore(dirt): Regenerate EF migrations on current main --- ...26215410_AddCipherBrowserHealthExclusions.Designer.cs} | 8 +++++++- ...=> 20261026215410_AddCipherBrowserHealthExclusions.cs} | 0 ...26215355_AddCipherBrowserHealthExclusions.Designer.cs} | 8 +++++++- ...=> 20261026215355_AddCipherBrowserHealthExclusions.cs} | 0 ...26215402_AddCipherBrowserHealthExclusions.Designer.cs} | 8 +++++++- ...=> 20261026215402_AddCipherBrowserHealthExclusions.cs} | 0 6 files changed, 21 insertions(+), 3 deletions(-) rename util/MySqlMigrations/Migrations/{20261026190333_AddCipherBrowserHealthExclusions.Designer.cs => 20261026215410_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/MySqlMigrations/Migrations/{20261026190333_AddCipherBrowserHealthExclusions.cs => 20261026215410_AddCipherBrowserHealthExclusions.cs} (100%) rename util/PostgresMigrations/Migrations/{20261026190223_AddCipherBrowserHealthExclusions.Designer.cs => 20261026215355_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/PostgresMigrations/Migrations/{20261026190223_AddCipherBrowserHealthExclusions.cs => 20261026215355_AddCipherBrowserHealthExclusions.cs} (100%) rename util/SqliteMigrations/Migrations/{20261026190237_AddCipherBrowserHealthExclusions.Designer.cs => 20261026215402_AddCipherBrowserHealthExclusions.Designer.cs} (99%) rename util/SqliteMigrations/Migrations/{20261026190237_AddCipherBrowserHealthExclusions.cs => 20261026215402_AddCipherBrowserHealthExclusions.cs} (100%) diff --git a/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs b/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs rename to util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs index 69f7eaee48c7..9f7c385f5654 100644 --- a/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.MySqlMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026190333_AddCipherBrowserHealthExclusions")] + [Migration("20261026215410_AddCipherBrowserHealthExclusions")] partial class AddCipherBrowserHealthExclusions { /// @@ -1788,6 +1788,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasMaxLength(300) .HasColumnType("varchar(300)"); + b.Property("InviteReminderDueDate") + .HasColumnType("datetime(6)"); + + b.Property("InviteRemindersSentCount") + .HasColumnType("tinyint unsigned"); + b.Property("Key") .HasColumnType("longtext"); diff --git a/util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.cs b/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20261026190333_AddCipherBrowserHealthExclusions.cs rename to util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.cs diff --git a/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs b/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs rename to util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs index 7f09b535bdd5..33fddf3df88c 100644 --- a/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs @@ -12,7 +12,7 @@ namespace Bit.PostgresMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026190223_AddCipherBrowserHealthExclusions")] + [Migration("20261026215355_AddCipherBrowserHealthExclusions")] partial class AddCipherBrowserHealthExclusions { /// @@ -1793,6 +1793,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("InviteReminderDueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InviteRemindersSentCount") + .HasColumnType("smallint"); + b.Property("Key") .HasColumnType("text"); diff --git a/util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.cs b/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.cs similarity index 100% rename from util/PostgresMigrations/Migrations/20261026190223_AddCipherBrowserHealthExclusions.cs rename to util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.cs diff --git a/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs b/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs similarity index 99% rename from util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs rename to util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs index 1ac946a01a3a..7349d82f104c 100644 --- a/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs @@ -11,7 +11,7 @@ namespace Bit.SqliteMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026190237_AddCipherBrowserHealthExclusions")] + [Migration("20261026215402_AddCipherBrowserHealthExclusions")] partial class AddCipherBrowserHealthExclusions { /// @@ -1777,6 +1777,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasMaxLength(300) .HasColumnType("TEXT"); + b.Property("InviteReminderDueDate") + .HasColumnType("TEXT"); + + b.Property("InviteRemindersSentCount") + .HasColumnType("INTEGER"); + b.Property("Key") .HasColumnType("TEXT"); diff --git a/util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.cs b/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20261026190237_AddCipherBrowserHealthExclusions.cs rename to util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.cs From 64a5baa5243f556a416070ac5c90e64a889fe128 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 27 Oct 2026 13:25:43 -0400 Subject: [PATCH 17/27] [PM-41931] chore(dirt): Re-date the SQL migration after main --- ...ons.sql => 2026-10-27_00_AddCipherBrowserHealthExclusions.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename util/Migrator/DbScripts/{2026-10-26_00_AddCipherBrowserHealthExclusions.sql => 2026-10-27_00_AddCipherBrowserHealthExclusions.sql} (100%) diff --git a/util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql similarity index 100% rename from util/Migrator/DbScripts/2026-10-26_00_AddCipherBrowserHealthExclusions.sql rename to util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql From 21ab4c84fddb14ba6032234f80353a7b8669d493 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 27 Oct 2026 14:11:06 -0400 Subject: [PATCH 18/27] [PM-41931] feat(dirt): Add an Exclude parameter to the exclusion procedure Lets the restore feature reuse the procedure. Passing 0 removes the user's key, because the SQL Server read treats any stored value as excluded. --- .../Dirt/CipherBrowserHealthExclusionRepository.cs | 2 +- .../Cipher_UpdateManyBrowserHealthExclusions.sql | 5 +++-- .../2026-10-27_00_AddCipherBrowserHealthExclusions.sql | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index 4f16c768abce..9f596d992a28 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -25,7 +25,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( "[dbo].[Cipher_UpdateManyBrowserHealthExclusions]", - new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId }, + new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId, Exclude = true }, commandType: CommandType.StoredProcedure); } } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql index 0c32e3736432..f1c82e1dc728 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql @@ -1,6 +1,7 @@ CREATE PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, - @UserId AS UNIQUEIDENTIFIER + @UserId AS UNIQUEIDENTIFIER, + @Exclude AS BIT AS BEGIN SET NOCOUNT ON @@ -11,7 +12,7 @@ BEGIN [BrowserHealthExclusions] = JSON_MODIFY( COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CAST(1 AS BIT) + IIF(@Exclude = 1, CAST(1 AS BIT), NULL) -- NULL removes the user's key ) FROM [dbo].[Cipher] C diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql index dcc82c5335d3..6f43676697ba 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql @@ -531,7 +531,8 @@ GO CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, - @UserId AS UNIQUEIDENTIFIER + @UserId AS UNIQUEIDENTIFIER, + @Exclude AS BIT AS BEGIN SET NOCOUNT ON @@ -542,7 +543,7 @@ BEGIN [BrowserHealthExclusions] = JSON_MODIFY( COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), - CAST(1 AS BIT) + IIF(@Exclude = 1, CAST(1 AS BIT), NULL) -- NULL removes the user's key ) FROM [dbo].[Cipher] C From ebf50668c39bb2dbbb948b7829a485f6f0fdf703 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 2 Nov 2026 10:22:08 -0500 Subject: [PATCH 19/27] [PM-41931] feat(dirt): Store the excluded risk categories per user Store { "": [""] } instead of { "": true }. The procedure and the EF repository take the risk category, and reads return the user's array as ExcludedRiskCategories. An exclude without a risk category now throws instead of saving [null]. Restore does not need a category. --- .../Models/Response/CipherResponseModel.cs | 6 +- ...ICipherBrowserHealthExclusionRepository.cs | 4 +- src/Core/Vault/Models/Data/CipherDetails.cs | 6 +- .../CipherBrowserHealthExclusionRepository.cs | 6 +- .../CipherBrowserHealthExclusionRepository.cs | 12 +-- .../Queries/UserCipherDetailsQuery.cs | 23 +++++- .../Vault/Repositories/CipherRepository.cs | 2 +- .../Queries/CipherDetailsQuery.cs | 2 +- ...pher_UpdateManyBrowserHealthExclusions.sql | 17 ++-- src/Sql/dbo/Vault/Functions/CipherDetails.sql | 10 +-- .../Cipher/CipherDetails_Create.sql | 2 +- .../CipherDetails_CreateWithCollections.sql | 2 +- .../Cipher/CipherDetails_ReadByIdUserId.sql | 4 +- .../Cipher/CipherDetails_Update.sql | 2 +- ...erBrowserHealthExclusionRepositoryTests.cs | 78 ++++++++++++------- ...27_00_AddCipherBrowserHealthExclusions.sql | 37 ++++----- 16 files changed, 130 insertions(+), 83 deletions(-) diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index 4ad2acbacd1f..e20a85019eb4 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -296,7 +296,9 @@ protected CipherResponseModel( Favorite = cipher.Favorite; Edit = cipher.Edit; ArchivedDate = cipher.ArchivedDate; - ExcludedFromBrowserHealth = cipher.ExcludedFromBrowserHealth; + ExcludedRiskCategories = cipher.ExcludedRiskCategories == null + ? null + : JsonSerializer.Deserialize>(cipher.ExcludedRiskCategories); ViewPassword = cipher.ViewPassword; Permissions = new CipherPermissionsResponseModel(user, cipher, organizationAbility); } @@ -313,7 +315,7 @@ public static CipherResponseModel From(FullCipherAccess access, CipherDetails ci public bool Edit { get; set; } public bool ViewPassword { get; set; } public DateTime? ArchivedDate { get; set; } - public bool ExcludedFromBrowserHealth { get; set; } + public IEnumerable? ExcludedRiskCategories { get; set; } public CipherPermissionsResponseModel Permissions { get; set; } } diff --git a/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs index 16864beb7e4e..3dad1325acc2 100644 --- a/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs +++ b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs @@ -2,6 +2,6 @@ public interface ICipherBrowserHealthExclusionRepository { - /// Excludes the user's personal, not-deleted logins from browser health. Returns how many were excluded. - Task ExcludeAsync(IEnumerable cipherIds, Guid userId); + /// Excludes the user's personal, not-deleted logins from browser health, recording the risk category, which must not be empty. Returns how many were excluded. + Task ExcludeAsync(IEnumerable cipherIds, Guid userId, string riskCategory); } diff --git a/src/Core/Vault/Models/Data/CipherDetails.cs b/src/Core/Vault/Models/Data/CipherDetails.cs index ffdf09e53186..eaf74cdbc7a9 100644 --- a/src/Core/Vault/Models/Data/CipherDetails.cs +++ b/src/Core/Vault/Models/Data/CipherDetails.cs @@ -11,8 +11,8 @@ public class CipherDetails : CipherOrganizationDetails public bool Manage { get; set; } // Per-user archived date from Archives JSON. public DateTime? ArchivedDate { get; set; } - // Per-user browser health exclusion flag from BrowserHealthExclusions JSON. - public bool ExcludedFromBrowserHealth { get; set; } + // Per-user risk categories (JSON array) from BrowserHealthExclusions JSON. + public string? ExcludedRiskCategories { get; set; } public CipherDetails() { } public CipherDetails(CipherOrganizationDetails cipher) @@ -55,7 +55,7 @@ public CipherDetailsWithCollections( Key = cipher.Key; FolderId = cipher.FolderId; ArchivedDate = cipher.ArchivedDate; - ExcludedFromBrowserHealth = cipher.ExcludedFromBrowserHealth; + ExcludedRiskCategories = cipher.ExcludedRiskCategories; Favorite = cipher.Favorite; Edit = cipher.Edit; ViewPassword = cipher.ViewPassword; diff --git a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index 9f596d992a28..fec73452bd4f 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -14,8 +14,10 @@ public CipherBrowserHealthExclusionRepository(GlobalSettings globalSettings) { } - public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) + public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, string riskCategory) { + ArgumentException.ThrowIfNullOrWhiteSpace(riskCategory); + var ids = cipherIds.ToList(); if (ids.Count == 0) { @@ -25,7 +27,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( "[dbo].[Cipher_UpdateManyBrowserHealthExclusions]", - new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId, Exclude = true }, + new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId, Exclude = true, RiskCategory = riskCategory }, commandType: CommandType.StoredProcedure); } } diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs index bc3e07ee9100..dba272bde6cd 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -15,8 +15,10 @@ public CipherBrowserHealthExclusionRepository(IServiceScopeFactory serviceScopeF { } - public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) + public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, string riskCategory) { + ArgumentException.ThrowIfNullOrWhiteSpace(riskCategory); + var ids = cipherIds.ToList(); if (ids.Count == 0) { @@ -33,10 +35,10 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId) foreach (var cipher in ciphers) { var exclusions = string.IsNullOrWhiteSpace(cipher.BrowserHealthExclusions) - ? new Dictionary() - : CoreHelpers.LoadClassFromJsonData>(cipher.BrowserHealthExclusions) - ?? new Dictionary(); - exclusions[userId] = true; + ? new Dictionary>() + : CoreHelpers.LoadClassFromJsonData>>(cipher.BrowserHealthExclusions) + ?? new Dictionary>(); + exclusions[userId] = [riskCategory]; cipher.BrowserHealthExclusions = CoreHelpers.ClassToJsonData(exclusions); } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index a5fa6b097470..a721302144d2 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -120,7 +120,7 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }), - ExcludedFromBrowserHealth = _userId.HasValue && c.BrowserHealthExclusions != null && c.BrowserHealthExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true") + ExcludedRiskCategories = GetExcludedRiskCategories(_userId, c.BrowserHealthExclusions) }); return union; } @@ -147,6 +147,27 @@ from cg in cg_g.DefaultIfEmpty() } + internal static string GetExcludedRiskCategories(Guid? userId, string browserHealthExclusions) + { + try + { + if (userId.HasValue && !string.IsNullOrWhiteSpace(browserHealthExclusions)) + { + var exclusions = JsonSerializer.Deserialize>>(browserHealthExclusions); + if (exclusions.TryGetValue(userId.Value, out var riskCategories)) + { + return JsonSerializer.Serialize(riskCategories); + } + } + + return null; + } + catch + { + return null; + } + } + private static Guid? GetFolderId(Guid? userId, Cipher cipher) { try diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 5026b0a30d17..259061fba9b9 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -518,7 +518,7 @@ public async Task> GetManyByUserIdAsync(Guid userId, OrganizationUseTotp = false, Key = c.Key, ArchivedDate = c.ArchivedDate, - ExcludedFromBrowserHealth = c.ExcludedFromBrowserHealth, + ExcludedRiskCategories = c.ExcludedRiskCategories, }; } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs index 1d53ac43458b..1e10ddcbd77e 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs @@ -37,7 +37,7 @@ public virtual IQueryable Run(DatabaseContext dbContext) ArchivedDate = !_userId.HasValue || c.Archives == null || !c.Archives.ToLowerInvariant().Contains(_userId.Value.ToString()) ? null : CoreHelpers.LoadClassFromJsonData>(c.Archives)[_userId.Value], - ExcludedFromBrowserHealth = _userId.HasValue && c.BrowserHealthExclusions != null && c.BrowserHealthExclusions.ToLowerInvariant().Contains($"\"{_userId}\":true"), + ExcludedRiskCategories = UserCipherDetailsQuery.GetExcludedRiskCategories(_userId, c.BrowserHealthExclusions), }; return query; } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql index f1c82e1dc728..9b095bfc0d1e 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql @@ -1,7 +1,8 @@ CREATE PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, - @Exclude AS BIT + @Exclude AS BIT, + @RiskCategory AS VARCHAR(50) = NULL AS BEGIN SET NOCOUNT ON @@ -9,11 +10,15 @@ BEGIN UPDATE C SET - [BrowserHealthExclusions] = JSON_MODIFY( - COALESCE(C.[BrowserHealthExclusions], N'{}'), - CONCAT('$."', @UserId, '"'), - IIF(@Exclude = 1, CAST(1 AS BIT), NULL) -- NULL removes the user's key - ) + [BrowserHealthExclusions] = CASE + WHEN @Exclude = 1 THEN JSON_MODIFY( + COALESCE(C.[BrowserHealthExclusions], N'{}'), + CONCAT('$."', @UserId, '"'), + JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) + ) + -- NULL removes the user's key + ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) + END FROM [dbo].[Cipher] C INNER JOIN @Ids I ON I.[Id] = C.[Id] diff --git a/src/Sql/dbo/Vault/Functions/CipherDetails.sql b/src/Sql/dbo/Vault/Functions/CipherDetails.sql index a694b21c27f4..56a3628f3b85 100644 --- a/src/Sql/dbo/Vault/Functions/CipherDetails.sql +++ b/src/Sql/dbo/Vault/Functions/CipherDetails.sql @@ -36,12 +36,8 @@ SELECT ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[Archives], CONCAT('$."', @UserId, '"'))) END [ArchivedDate], CASE - WHEN - @UserId IS NULL - OR C.[BrowserHealthExclusions] IS NULL - OR JSON_VALUE(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL - THEN 0 - ELSE 1 - END [ExcludedFromBrowserHealth] + WHEN @UserId IS NULL THEN NULL + ELSE JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + END [ExcludedRiskCategories] FROM [dbo].[Cipher] C; diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql index da9378328376..52f43bd32e75 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql index 450d5cd152d4..6c2da7f82c13 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql index 7c44a1f8068f..368be0602e0b 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql @@ -21,7 +21,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromBrowserHealth], + [ExcludedRiskCategories], -- The highest rank holds Manage as 4, Edit as 2 and ViewPassword as 1; each flag is one part of it (MAX(R.[Rank]) / 2) % 2 AS [Edit], MAX(R.[Rank]) % 2 AS [ViewPassword], @@ -50,5 +50,5 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromBrowserHealth] + [ExcludedRiskCategories] END diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index 52ca63a90800..00aac0964c64 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -20,7 +20,7 @@ @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index 8015856ddf32..714479350471 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -1,9 +1,11 @@ -using Bit.Core.Dirt.Repositories; +using System.Text.Json; +using Bit.Core.Dirt.Repositories; using Bit.Core.Entities; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Enums; +using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Repositories; using Bit.Infrastructure.IntegrationTest.AdminConsole; using Xunit; @@ -13,17 +15,17 @@ namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; public class CipherBrowserHealthExclusionRepositoryTests { [DatabaseTheory, DatabaseData] - public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndSetsUsersExcludedFromBrowserHealth( + public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndStoresRiskCategory( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - var excluded = await sut.ExcludeAsync([cipher.Id], user.Id); + var excluded = await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); Assert.Equal(1, excluded); var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); - Assert.True(details.ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories(details)); } [DatabaseTheory, DatabaseData] @@ -41,7 +43,7 @@ public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( Data = "", }); - Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id, "weak")); } [DatabaseTheory, DatabaseData] @@ -51,7 +53,7 @@ public async Task ExcludeAsync_NonLoginCipher_ReturnsZero( var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id, CipherType.SecureNote); - Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id, "weak")); } [DatabaseTheory, DatabaseData] @@ -62,7 +64,7 @@ public async Task ExcludeAsync_DeletedCipher_ReturnsZero( var cipher = await CreateCipherAsync(cipherRepository, user.Id); await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); - Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id)); + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], user.Id, "weak")); } [DatabaseTheory, DatabaseData] @@ -73,19 +75,32 @@ public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( var otherUser = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, owner.Id); - Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id)); - Assert.False((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromBrowserHealth); + Assert.Equal(0, await sut.ExcludeAsync([cipher.Id], otherUser.Id, "weak")); + Assert.Null((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedRiskCategories); } [DatabaseTheory, DatabaseData] - public async Task ExcludeAsync_AlreadyExcluded_ReturnsOne( + public async Task ExcludeAsync_AlreadyExcluded_ReturnsOneAndReplacesRiskCategory( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); - Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id)); + Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id, "exposed")); + Assert.Equal(["exposed"], RiskCategories(await cipherRepository.GetByIdAsync(cipher.Id, user.Id))); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_MissingRiskCategory_ThrowsAndStoresNothing( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await userRepository.CreateTestUserAsync(); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + + await Assert.ThrowsAsync(() => sut.ExcludeAsync([cipher.Id], user.Id, null!)); + await Assert.ThrowsAsync(() => sut.ExcludeAsync([cipher.Id], user.Id, " ")); + Assert.Null((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedRiskCategories); } [DatabaseTheory, DatabaseData] @@ -96,7 +111,7 @@ public async Task ExcludeAsync_DoesNotChangeCipherRevisionDate( var cipher = await CreateCipherAsync(cipherRepository, user.Id); var before = (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).RevisionDate; - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); Assert.Equal(before, (await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).RevisionDate); } @@ -109,37 +124,37 @@ public async Task ExcludeAsync_BumpsAccountRevisionDate( var cipher = await CreateCipherAsync(cipherRepository, user.Id); var before = (await userRepository.GetByIdAsync(user.Id))!.AccountRevisionDate; - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); Assert.True((await userRepository.GetByIdAsync(user.Id))!.AccountRevisionDate > before); } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_ReturnsExcludedFromBrowserHealth( + public async Task GetManyByUserIdAsync_ReturnsExcludedRiskCategories( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var excludedCipher = await CreateCipherAsync(cipherRepository, user.Id); var otherCipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([excludedCipher.Id], user.Id); + await sut.ExcludeAsync([excludedCipher.Id], user.Id, "weak"); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); - Assert.True(ciphers.Single(c => c.Id == excludedCipher.Id).ExcludedFromBrowserHealth); - Assert.False(ciphers.Single(c => c.Id == otherCipher.Id).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories(ciphers.Single(c => c.Id == excludedCipher.Id))); + Assert.Null(ciphers.Single(c => c.Id == otherCipher.Id).ExcludedRiskCategories); } [DatabaseTheory, DatabaseData] - public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedFromBrowserHealth( + public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedRiskCategories( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: false); - Assert.True(ciphers.Single(c => c.Id == cipher.Id).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories(ciphers.Single(c => c.Id == cipher.Id))); } [DatabaseTheory, DatabaseData] @@ -148,13 +163,13 @@ public async Task ReplaceAsync_CipherDetails_PreservesBrowserHealthExclusions( { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); var details = await cipherRepository.GetByIdAsync(cipher.Id, user.Id); details.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(details); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)))); } [DatabaseTheory, DatabaseData] @@ -163,12 +178,12 @@ public async Task ReplaceAsync_Cipher_PreservesBrowserHealthExclusions( { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); cipher.Data = "{\"updated\":true}"; await cipherRepository.ReplaceAsync(cipher); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)))); } [DatabaseTheory, DatabaseData] @@ -177,11 +192,11 @@ public async Task SoftDeleteAsync_KeepsBrowserHealthExclusion( { var user = await userRepository.CreateTestUserAsync(); var cipher = await CreateCipherAsync(cipherRepository, user.Id); - await sut.ExcludeAsync([cipher.Id], user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); await cipherRepository.SoftDeleteAsync([cipher.Id], user.Id); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories((await cipherRepository.GetByIdAsync(cipher.Id, user.Id)))); } [DatabaseTheory, DatabaseData] @@ -202,14 +217,17 @@ public async Task ReplaceAsync_ShareToOrg_KeepsOwnersExclusionOnly( new() { Id = memberOrgUser.Id, Manage = true }, }); var cipher = await CreateCipherAsync(cipherRepository, owner.Id); - await sut.ExcludeAsync([cipher.Id], owner.Id); + await sut.ExcludeAsync([cipher.Id], owner.Id, "weak"); cipher.OrganizationId = org.Id; await cipherRepository.ReplaceAsync(cipher, [collection.Id]); - Assert.True((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)).ExcludedFromBrowserHealth); - Assert.False((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ExcludedFromBrowserHealth); + Assert.Equal(["weak"], RiskCategories((await cipherRepository.GetByIdAsync(cipher.Id, owner.Id)))); + Assert.Null((await cipherRepository.GetManyByUserIdAsync(member.Id)).Single(c => c.Id == cipher.Id).ExcludedRiskCategories); } + private static List RiskCategories(CipherDetails cipher) => + JsonSerializer.Deserialize>(cipher.ExcludedRiskCategories); + private static Task CreateCipherAsync(ICipherRepository cipherRepository, Guid userId, CipherType type = CipherType.Login) => cipherRepository.CreateAsync(new Cipher { Type = type, UserId = userId, Data = "" }); diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql index 6f43676697ba..a6a9a4b849f2 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql @@ -43,13 +43,9 @@ SELECT ELSE TRY_CONVERT(DATETIME2(7), JSON_VALUE(C.[Archives], CONCAT('$."', @UserId, '"'))) END [ArchivedDate], CASE - WHEN - @UserId IS NULL - OR C.[BrowserHealthExclusions] IS NULL - OR JSON_VALUE(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL - THEN 0 - ELSE 1 - END [ExcludedFromBrowserHealth] + WHEN @UserId IS NULL THEN NULL + ELSE JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + END [ExcludedRiskCategories] FROM [dbo].[Cipher] C; GO @@ -296,7 +292,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN @@ -371,7 +367,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS @@ -415,7 +411,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] @Key VARCHAR(MAX) = NULL, @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used - @ExcludedFromBrowserHealth BIT = NULL, -- not used + @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN @@ -501,7 +497,7 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromBrowserHealth], + [ExcludedRiskCategories], MAX ([Edit]) AS [Edit], MAX ([ViewPassword]) AS [ViewPassword], MAX ([Manage]) AS [Manage] @@ -525,14 +521,15 @@ SELECT [Key], [OrganizationUseTotp], [ArchivedDate], - [ExcludedFromBrowserHealth] + [ExcludedRiskCategories] END GO CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, - @Exclude AS BIT + @Exclude AS BIT, + @RiskCategory AS VARCHAR(50) = NULL AS BEGIN SET NOCOUNT ON @@ -540,11 +537,15 @@ BEGIN UPDATE C SET - [BrowserHealthExclusions] = JSON_MODIFY( - COALESCE(C.[BrowserHealthExclusions], N'{}'), - CONCAT('$."', @UserId, '"'), - IIF(@Exclude = 1, CAST(1 AS BIT), NULL) -- NULL removes the user's key - ) + [BrowserHealthExclusions] = CASE + WHEN @Exclude = 1 THEN JSON_MODIFY( + COALESCE(C.[BrowserHealthExclusions], N'{}'), + CONCAT('$."', @UserId, '"'), + JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) + ) + -- NULL removes the user's key + ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) + END FROM [dbo].[Cipher] C INNER JOIN @Ids I ON I.[Id] = C.[Id] From 1c8b82a69537772a7f1d2bc58df893cafd5fdbb1 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 3 Nov 2026 16:38:23 -0500 Subject: [PATCH 20/27] [PM-41931] feat(dirt): Add the risk category to the user's exclusions Excluding a login adds the risk category to the user's array instead of replacing it, matching the tech breakdown's PUT. A category that is already in the array is kept once. Restore still removes the user's entry. --- .../ICipherBrowserHealthExclusionRepository.cs | 2 +- .../CipherBrowserHealthExclusionRepository.cs | 9 ++++++++- .../Cipher_UpdateManyBrowserHealthExclusions.sql | 14 +++++++++++++- ...ipherBrowserHealthExclusionRepositoryTests.cs | 16 ++++++++++++++-- ...10-27_00_AddCipherBrowserHealthExclusions.sql | 14 +++++++++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs index 3dad1325acc2..7450fd5afc47 100644 --- a/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs +++ b/src/Core/Dirt/Repositories/ICipherBrowserHealthExclusionRepository.cs @@ -2,6 +2,6 @@ public interface ICipherBrowserHealthExclusionRepository { - /// Excludes the user's personal, not-deleted logins from browser health, recording the risk category, which must not be empty. Returns how many were excluded. + /// Excludes the user's personal, not-deleted logins from browser health, adding the risk category to the user's list once. The category must not be empty. Returns how many were excluded. Task ExcludeAsync(IEnumerable cipherIds, Guid userId, string riskCategory); } diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs index dba272bde6cd..516b3572e4b4 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -38,7 +38,14 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, st ? new Dictionary>() : CoreHelpers.LoadClassFromJsonData>>(cipher.BrowserHealthExclusions) ?? new Dictionary>(); - exclusions[userId] = [riskCategory]; + if (!exclusions.TryGetValue(userId, out var riskCategories) || riskCategories == null) + { + exclusions[userId] = [riskCategory]; + } + else if (!riskCategories.Contains(riskCategory)) + { + riskCategories.Add(riskCategory); + } cipher.BrowserHealthExclusions = CoreHelpers.ClassToJsonData(exclusions); } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql index 9b095bfc0d1e..e500d0a89b6f 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql @@ -11,11 +11,23 @@ BEGIN C SET [BrowserHealthExclusions] = CASE - WHEN @Exclude = 1 THEN JSON_MODIFY( + -- First exclusion for this user: start the user's array + WHEN @Exclude = 1 AND JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) ) + -- Category already excluded: keep the value + WHEN @Exclude = 1 AND EXISTS ( + SELECT 1 + FROM OPENJSON(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + WHERE [value] = @RiskCategory + ) THEN C.[BrowserHealthExclusions] + WHEN @Exclude = 1 THEN JSON_MODIFY( + C.[BrowserHealthExclusions], + CONCAT('append $."', @UserId, '"'), + @RiskCategory + ) -- NULL removes the user's key ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) END diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index 714479350471..6c7fa247df55 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -80,7 +80,7 @@ public async Task ExcludeAsync_OtherUsersCipher_ReturnsZero( } [DatabaseTheory, DatabaseData] - public async Task ExcludeAsync_AlreadyExcluded_ReturnsOneAndReplacesRiskCategory( + public async Task ExcludeAsync_AnotherRiskCategory_ReturnsOneAndAddsIt( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -88,7 +88,19 @@ public async Task ExcludeAsync_AlreadyExcluded_ReturnsOneAndReplacesRiskCategory await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id, "exposed")); - Assert.Equal(["exposed"], RiskCategories(await cipherRepository.GetByIdAsync(cipher.Id, user.Id))); + Assert.Equal(["weak", "exposed"], RiskCategories(await cipherRepository.GetByIdAsync(cipher.Id, user.Id))); + } + + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_SameRiskCategoryTwice_StoresItOnce( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await userRepository.CreateTestUserAsync(); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); + + Assert.Equal(1, await sut.ExcludeAsync([cipher.Id], user.Id, "weak")); + Assert.Equal(["weak"], RiskCategories(await cipherRepository.GetByIdAsync(cipher.Id, user.Id))); } [DatabaseTheory, DatabaseData] diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql index a6a9a4b849f2..ec1ac2e7cbfd 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql @@ -538,11 +538,23 @@ BEGIN C SET [BrowserHealthExclusions] = CASE - WHEN @Exclude = 1 THEN JSON_MODIFY( + -- First exclusion for this user: start the user's array + WHEN @Exclude = 1 AND JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) ) + -- Category already excluded: keep the value + WHEN @Exclude = 1 AND EXISTS ( + SELECT 1 + FROM OPENJSON(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + WHERE [value] = @RiskCategory + ) THEN C.[BrowserHealthExclusions] + WHEN @Exclude = 1 THEN JSON_MODIFY( + C.[BrowserHealthExclusions], + CONCAT('append $."', @UserId, '"'), + @RiskCategory + ) -- NULL removes the user's key ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) END From 81daeaff15539176483d62650fb57f0976cd989c Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 3 Nov 2026 16:43:12 -0500 Subject: [PATCH 21/27] [PM-41931] refactor(dirt): Rename the column to Cipher.Exclusions Use the name agreed in the tech breakdown review: Cipher.Exclusions, and Cipher_UpdateManyExclusions for the procedure that writes it. The EF migrations are regenerated as AddCipherExclusions. --- src/Core/Vault/Entities/Cipher.cs | 2 +- src/Core/Vault/Models/Data/CipherDetails.cs | 2 +- .../CipherBrowserHealthExclusionRepository.cs | 2 +- .../CipherBrowserHealthExclusionRepository.cs | 6 ++-- .../Queries/UserCipherDetailsQuery.cs | 6 ++-- .../Vault/Repositories/CipherRepository.cs | 4 +-- .../Queries/CipherDetailsQuery.cs | 2 +- ...ns.sql => Cipher_UpdateManyExclusions.sql} | 16 ++++----- src/Sql/dbo/Vault/Functions/CipherDetails.sql | 2 +- .../Cipher/CipherDetails_Create.sql | 2 +- .../CipherDetails_CreateWithCollections.sql | 2 +- .../Cipher/CipherDetails_Update.sql | 2 +- .../Cipher/Cipher_Create.sql | 2 +- .../Cipher/Cipher_CreateWithCollections.sql | 2 +- .../Cipher/Cipher_Update.sql | 2 +- .../Cipher/Cipher_UpdateWithCollections.sql | 2 +- src/Sql/dbo/Vault/Tables/Cipher.sql | 2 +- ...erBrowserHealthExclusionRepositoryTests.cs | 4 +-- ... => 2026-10-27_00_AddCipherExclusions.sql} | 36 +++++++++---------- ...103214024_AddCipherExclusions.Designer.cs} | 10 +++--- ... => 20261103214024_AddCipherExclusions.cs} | 6 ++-- .../DatabaseContextModelSnapshot.cs | 6 ++-- ...103214007_AddCipherExclusions.Designer.cs} | 10 +++--- ... => 20261103214007_AddCipherExclusions.cs} | 6 ++-- .../DatabaseContextModelSnapshot.cs | 6 ++-- ...103214017_AddCipherExclusions.Designer.cs} | 10 +++--- ... => 20261103214017_AddCipherExclusions.cs} | 6 ++-- .../DatabaseContextModelSnapshot.cs | 6 ++-- 28 files changed, 82 insertions(+), 82 deletions(-) rename src/Sql/dbo/Dirt/Stored Procedures/{Cipher_UpdateManyBrowserHealthExclusions.sql => Cipher_UpdateManyExclusions.sql} (66%) rename util/Migrator/DbScripts/{2026-10-27_00_AddCipherBrowserHealthExclusions.sql => 2026-10-27_00_AddCipherExclusions.sql} (92%) rename util/MySqlMigrations/Migrations/{20261026215410_AddCipherBrowserHealthExclusions.Designer.cs => 20261103214024_AddCipherExclusions.Designer.cs} (99%) rename util/MySqlMigrations/Migrations/{20261026215410_AddCipherBrowserHealthExclusions.cs => 20261103214024_AddCipherExclusions.cs} (79%) rename util/PostgresMigrations/Migrations/{20261026215355_AddCipherBrowserHealthExclusions.Designer.cs => 20261103214007_AddCipherExclusions.Designer.cs} (99%) rename util/PostgresMigrations/Migrations/{20261026215355_AddCipherBrowserHealthExclusions.cs => 20261103214007_AddCipherExclusions.cs} (77%) rename util/SqliteMigrations/Migrations/{20261026215402_AddCipherBrowserHealthExclusions.Designer.cs => 20261103214017_AddCipherExclusions.Designer.cs} (99%) rename util/SqliteMigrations/Migrations/{20261026215402_AddCipherBrowserHealthExclusions.cs => 20261103214017_AddCipherExclusions.cs} (77%) diff --git a/src/Core/Vault/Entities/Cipher.cs b/src/Core/Vault/Entities/Cipher.cs index 59d4df6d5b7f..4980badac3a6 100644 --- a/src/Core/Vault/Entities/Cipher.cs +++ b/src/Core/Vault/Entities/Cipher.cs @@ -27,7 +27,7 @@ public class Cipher : ITableObject, ICloneable public Enums.CipherRepromptType? Reprompt { get; set; } public string Key { get; set; } public string Archives { get; set; } - public string BrowserHealthExclusions { get; set; } + public string Exclusions { get; set; } public void SetNewId() { diff --git a/src/Core/Vault/Models/Data/CipherDetails.cs b/src/Core/Vault/Models/Data/CipherDetails.cs index eaf74cdbc7a9..3c07fa144ccc 100644 --- a/src/Core/Vault/Models/Data/CipherDetails.cs +++ b/src/Core/Vault/Models/Data/CipherDetails.cs @@ -11,7 +11,7 @@ public class CipherDetails : CipherOrganizationDetails public bool Manage { get; set; } // Per-user archived date from Archives JSON. public DateTime? ArchivedDate { get; set; } - // Per-user risk categories (JSON array) from BrowserHealthExclusions JSON. + // Per-user risk categories (JSON array) from the Exclusions JSON. public string? ExcludedRiskCategories { get; set; } public CipherDetails() { } diff --git a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs index fec73452bd4f..fbbfa46774e9 100644 --- a/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -26,7 +26,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, st await using var connection = new SqlConnection(ConnectionString); return await connection.ExecuteScalarAsync( - "[dbo].[Cipher_UpdateManyBrowserHealthExclusions]", + "[dbo].[Cipher_UpdateManyExclusions]", new { Ids = ids.ToGuidIdArrayTVP(), UserId = userId, Exclude = true, RiskCategory = riskCategory }, commandType: CommandType.StoredProcedure); } diff --git a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs index 516b3572e4b4..81ebba82705f 100644 --- a/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/CipherBrowserHealthExclusionRepository.cs @@ -34,9 +34,9 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, st foreach (var cipher in ciphers) { - var exclusions = string.IsNullOrWhiteSpace(cipher.BrowserHealthExclusions) + var exclusions = string.IsNullOrWhiteSpace(cipher.Exclusions) ? new Dictionary>() - : CoreHelpers.LoadClassFromJsonData>>(cipher.BrowserHealthExclusions) + : CoreHelpers.LoadClassFromJsonData>>(cipher.Exclusions) ?? new Dictionary>(); if (!exclusions.TryGetValue(userId, out var riskCategories) || riskCategories == null) { @@ -46,7 +46,7 @@ public async Task ExcludeAsync(IEnumerable cipherIds, Guid userId, st { riskCategories.Add(riskCategory); } - cipher.BrowserHealthExclusions = CoreHelpers.ClassToJsonData(exclusions); + cipher.Exclusions = CoreHelpers.ClassToJsonData(exclusions); } if (ciphers.Count > 0) diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index a721302144d2..2f275ef7ece1 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -72,7 +72,7 @@ from cg in cg_g.DefaultIfEmpty() c.Reprompt, c.Key, c.Archives, - c.BrowserHealthExclusions + c.Exclusions }; var query2 = from c in dbContext.Ciphers @@ -97,7 +97,7 @@ from cg in cg_g.DefaultIfEmpty() c.Reprompt, c.Key, c.Archives, - c.BrowserHealthExclusions + c.Exclusions }; var union = query.Union(query2).Select(c => new CipherDetails @@ -120,7 +120,7 @@ from cg in cg_g.DefaultIfEmpty() OrganizationUseTotp = c.OrganizationUseTotp, Key = c.Key, ArchivedDate = GetArchivedDate(_userId, new Cipher { Id = c.Id, Archives = c.Archives }), - ExcludedRiskCategories = GetExcludedRiskCategories(_userId, c.BrowserHealthExclusions) + ExcludedRiskCategories = GetExcludedRiskCategories(_userId, c.Exclusions) }); return union; } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index 259061fba9b9..1f85dd88b803 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -585,7 +585,7 @@ public override async Task ReplaceAsync(Core.Vault.Entities.Cipher cipher) { var mappedEntity = Mapper.Map(cipher); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); - dbContext.Entry(entity).Property(c => c.BrowserHealthExclusions).IsModified = false; + dbContext.Entry(entity).Property(c => c.Exclusions).IsModified = false; await dbContext.SaveChangesAsync(); } } @@ -664,7 +664,7 @@ public async Task ReplaceAsync(CipherDetails cipher) var mappedEntity = Mapper.Map(cipher); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); - dbContext.Entry(entity).Property(c => c.BrowserHealthExclusions).IsModified = false; + dbContext.Entry(entity).Property(c => c.Exclusions).IsModified = false; if (cipher.OrganizationId.HasValue) { diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs index 1e10ddcbd77e..e1372ce1f557 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherDetailsQuery.cs @@ -37,7 +37,7 @@ public virtual IQueryable Run(DatabaseContext dbContext) ArchivedDate = !_userId.HasValue || c.Archives == null || !c.Archives.ToLowerInvariant().Contains(_userId.Value.ToString()) ? null : CoreHelpers.LoadClassFromJsonData>(c.Archives)[_userId.Value], - ExcludedRiskCategories = UserCipherDetailsQuery.GetExcludedRiskCategories(_userId, c.BrowserHealthExclusions), + ExcludedRiskCategories = UserCipherDetailsQuery.GetExcludedRiskCategories(_userId, c.Exclusions), }; return query; } diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql similarity index 66% rename from src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql rename to src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql index e500d0a89b6f..ee0108345528 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyBrowserHealthExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] +CREATE PROCEDURE [dbo].[Cipher_UpdateManyExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, @Exclude AS BIT, @@ -10,26 +10,26 @@ BEGIN UPDATE C SET - [BrowserHealthExclusions] = CASE + [Exclusions] = CASE -- First exclusion for this user: start the user's array - WHEN @Exclude = 1 AND JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( - COALESCE(C.[BrowserHealthExclusions], N'{}'), + WHEN @Exclude = 1 AND JSON_QUERY(C.[Exclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( + COALESCE(C.[Exclusions], N'{}'), CONCAT('$."', @UserId, '"'), JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) ) -- Category already excluded: keep the value WHEN @Exclude = 1 AND EXISTS ( SELECT 1 - FROM OPENJSON(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + FROM OPENJSON(C.[Exclusions], CONCAT('$."', @UserId, '"')) WHERE [value] = @RiskCategory - ) THEN C.[BrowserHealthExclusions] + ) THEN C.[Exclusions] WHEN @Exclude = 1 THEN JSON_MODIFY( - C.[BrowserHealthExclusions], + C.[Exclusions], CONCAT('append $."', @UserId, '"'), @RiskCategory ) -- NULL removes the user's key - ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) + ELSE JSON_MODIFY(COALESCE(C.[Exclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) END FROM [dbo].[Cipher] C diff --git a/src/Sql/dbo/Vault/Functions/CipherDetails.sql b/src/Sql/dbo/Vault/Functions/CipherDetails.sql index 56a3628f3b85..854862be128e 100644 --- a/src/Sql/dbo/Vault/Functions/CipherDetails.sql +++ b/src/Sql/dbo/Vault/Functions/CipherDetails.sql @@ -37,7 +37,7 @@ SELECT END [ArchivedDate], CASE WHEN @UserId IS NULL THEN NULL - ELSE JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + ELSE JSON_QUERY(C.[Exclusions], CONCAT('$."', @UserId, '"')) END [ExcludedRiskCategories] FROM [dbo].[Cipher] C; diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql index 52f43bd32e75..152b2ad0cacd 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Create.sql @@ -21,7 +21,7 @@ @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql index 6c2da7f82c13..3468268d9931 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_CreateWithCollections.sql @@ -21,7 +21,7 @@ @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index 00aac0964c64..f67db7bcd871 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -21,7 +21,7 @@ @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql index 7d7c85b4babc..faee2f846689 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Create.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql index 79289d1afc32..482ac678efdd 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_CreateWithCollections.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql index 90ace7ac0951..a0f68c76130d 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql index e710796e13c9..8bff68215d6f 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_UpdateWithCollections.sql @@ -13,7 +13,7 @@ @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN diff --git a/src/Sql/dbo/Vault/Tables/Cipher.sql b/src/Sql/dbo/Vault/Tables/Cipher.sql index 37d42838379e..61cb56a0bba6 100644 --- a/src/Sql/dbo/Vault/Tables/Cipher.sql +++ b/src/Sql/dbo/Vault/Tables/Cipher.sql @@ -15,7 +15,7 @@ CREATE TABLE [dbo].[Cipher] ( [Key] VARCHAR(MAX) NULL, [ArchivedDate] DATETIME2 (7) NULL, [Archives] NVARCHAR(MAX) NULL, - [BrowserHealthExclusions] NVARCHAR(MAX) NULL, + [Exclusions] NVARCHAR(MAX) NULL, CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index 6c7fa247df55..6783311a7989 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -170,7 +170,7 @@ public async Task GetManyByUserIdAsync_WithoutOrganizations_ReturnsExcludedRiskC } [DatabaseTheory, DatabaseData] - public async Task ReplaceAsync_CipherDetails_PreservesBrowserHealthExclusions( + public async Task ReplaceAsync_CipherDetails_PreservesExclusions( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); @@ -185,7 +185,7 @@ public async Task ReplaceAsync_CipherDetails_PreservesBrowserHealthExclusions( } [DatabaseTheory, DatabaseData] - public async Task ReplaceAsync_Cipher_PreservesBrowserHealthExclusions( + public async Task ReplaceAsync_Cipher_PreservesExclusions( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) { var user = await userRepository.CreateTestUserAsync(); diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql similarity index 92% rename from util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql rename to util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql index ec1ac2e7cbfd..f3db9f1fe853 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherBrowserHealthExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql @@ -1,7 +1,7 @@ -IF COL_LENGTH('[dbo].[Cipher]', 'BrowserHealthExclusions') IS NULL +IF COL_LENGTH('[dbo].[Cipher]', 'Exclusions') IS NULL BEGIN ALTER TABLE [dbo].[Cipher] - ADD [BrowserHealthExclusions] NVARCHAR(MAX) NULL; + ADD [Exclusions] NVARCHAR(MAX) NULL; END GO @@ -44,7 +44,7 @@ SELECT END [ArchivedDate], CASE WHEN @UserId IS NULL THEN NULL - ELSE JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + ELSE JSON_QUERY(C.[Exclusions], CONCAT('$."', @UserId, '"')) END [ExcludedRiskCategories] FROM [dbo].[Cipher] C; @@ -71,7 +71,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -135,7 +135,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -185,7 +185,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_CreateWithCollections] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -220,7 +220,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateWithCollections] @Reprompt TINYINT, @Key VARCHAR(MAX) = NULL, @Archives NVARCHAR(MAX) = NULL, - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -293,7 +293,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create] @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -368,7 +368,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections] @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL, -- not used + @Exclusions NVARCHAR(MAX) = NULL, -- not used @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN @@ -412,7 +412,7 @@ CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] @ArchivedDate DATETIME2(7) = NULL, @Archives NVARCHAR(MAX) = NULL, -- not used @ExcludedRiskCategories NVARCHAR(MAX) = NULL, -- not used - @BrowserHealthExclusions NVARCHAR(MAX) = NULL -- not used + @Exclusions NVARCHAR(MAX) = NULL -- not used AS BEGIN SET NOCOUNT ON @@ -525,7 +525,7 @@ SELECT END GO -CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyBrowserHealthExclusions] +CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, @Exclude AS BIT, @@ -537,26 +537,26 @@ BEGIN UPDATE C SET - [BrowserHealthExclusions] = CASE + [Exclusions] = CASE -- First exclusion for this user: start the user's array - WHEN @Exclude = 1 AND JSON_QUERY(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( - COALESCE(C.[BrowserHealthExclusions], N'{}'), + WHEN @Exclude = 1 AND JSON_QUERY(C.[Exclusions], CONCAT('$."', @UserId, '"')) IS NULL THEN JSON_MODIFY( + COALESCE(C.[Exclusions], N'{}'), CONCAT('$."', @UserId, '"'), JSON_QUERY(JSON_MODIFY(N'[]', 'append $', @RiskCategory)) ) -- Category already excluded: keep the value WHEN @Exclude = 1 AND EXISTS ( SELECT 1 - FROM OPENJSON(C.[BrowserHealthExclusions], CONCAT('$."', @UserId, '"')) + FROM OPENJSON(C.[Exclusions], CONCAT('$."', @UserId, '"')) WHERE [value] = @RiskCategory - ) THEN C.[BrowserHealthExclusions] + ) THEN C.[Exclusions] WHEN @Exclude = 1 THEN JSON_MODIFY( - C.[BrowserHealthExclusions], + C.[Exclusions], CONCAT('append $."', @UserId, '"'), @RiskCategory ) -- NULL removes the user's key - ELSE JSON_MODIFY(COALESCE(C.[BrowserHealthExclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) + ELSE JSON_MODIFY(COALESCE(C.[Exclusions], N'{}'), CONCAT('$."', @UserId, '"'), NULL) END FROM [dbo].[Cipher] C diff --git a/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs b/util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.Designer.cs similarity index 99% rename from util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs rename to util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.Designer.cs index 9f7c385f5654..188aaee7ead2 100644 --- a/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.Designer.cs @@ -12,8 +12,8 @@ namespace Bit.MySqlMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026215410_AddCipherBrowserHealthExclusions")] - partial class AddCipherBrowserHealthExclusions + [Migration("20261103214024_AddCipherExclusions")] + partial class AddCipherExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3331,9 +3331,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("longtext"); - b.Property("BrowserHealthExclusions") - .HasColumnType("longtext"); - b.Property("CreationDate") .HasColumnType("datetime(6)"); @@ -3343,6 +3340,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("datetime(6)"); + b.Property("Exclusions") + .HasColumnType("longtext"); + b.Property("Favorites") .HasColumnType("longtext"); diff --git a/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.cs b/util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.cs similarity index 79% rename from util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.cs rename to util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.cs index c1cf1f0c095a..5518fa87fe8e 100644 --- a/util/MySqlMigrations/Migrations/20261026215410_AddCipherBrowserHealthExclusions.cs +++ b/util/MySqlMigrations/Migrations/20261103214024_AddCipherExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.MySqlMigrations.Migrations; /// -public partial class AddCipherBrowserHealthExclusions : Migration +public partial class AddCipherExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher", type: "longtext", nullable: true) @@ -22,7 +22,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher"); } } diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 494983089eae..8fe5555ea745 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3328,9 +3328,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("longtext"); - b.Property("BrowserHealthExclusions") - .HasColumnType("longtext"); - b.Property("CreationDate") .HasColumnType("datetime(6)"); @@ -3340,6 +3337,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("datetime(6)"); + b.Property("Exclusions") + .HasColumnType("longtext"); + b.Property("Favorites") .HasColumnType("longtext"); diff --git a/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs b/util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.Designer.cs similarity index 99% rename from util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs rename to util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.Designer.cs index 33fddf3df88c..ffe60a4a5dcd 100644 --- a/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.Designer.cs @@ -12,8 +12,8 @@ namespace Bit.PostgresMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026215355_AddCipherBrowserHealthExclusions")] - partial class AddCipherBrowserHealthExclusions + [Migration("20261103214007_AddCipherExclusions")] + partial class AddCipherExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3337,9 +3337,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("text"); - b.Property("BrowserHealthExclusions") - .HasColumnType("text"); - b.Property("CreationDate") .HasColumnType("timestamp with time zone"); @@ -3349,6 +3346,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("timestamp with time zone"); + b.Property("Exclusions") + .HasColumnType("text"); + b.Property("Favorites") .HasColumnType("text"); diff --git a/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.cs b/util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.cs similarity index 77% rename from util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.cs rename to util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.cs index d82fc9e41fd3..10aa52200971 100644 --- a/util/PostgresMigrations/Migrations/20261026215355_AddCipherBrowserHealthExclusions.cs +++ b/util/PostgresMigrations/Migrations/20261103214007_AddCipherExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.PostgresMigrations.Migrations; /// -public partial class AddCipherBrowserHealthExclusions : Migration +public partial class AddCipherExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher", type: "text", nullable: true); @@ -21,7 +21,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher"); } } diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 7f0918973903..28a4a5bc5f41 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3334,9 +3334,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("text"); - b.Property("BrowserHealthExclusions") - .HasColumnType("text"); - b.Property("CreationDate") .HasColumnType("timestamp with time zone"); @@ -3346,6 +3343,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("timestamp with time zone"); + b.Property("Exclusions") + .HasColumnType("text"); + b.Property("Favorites") .HasColumnType("text"); diff --git a/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs b/util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.Designer.cs similarity index 99% rename from util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs rename to util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.Designer.cs index 7349d82f104c..e35724c4a855 100644 --- a/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.Designer.cs +++ b/util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.Designer.cs @@ -11,8 +11,8 @@ namespace Bit.SqliteMigrations.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20261026215402_AddCipherBrowserHealthExclusions")] - partial class AddCipherBrowserHealthExclusions + [Migration("20261103214017_AddCipherExclusions")] + partial class AddCipherExclusions { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3320,9 +3320,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("TEXT"); - b.Property("BrowserHealthExclusions") - .HasColumnType("TEXT"); - b.Property("CreationDate") .HasColumnType("TEXT"); @@ -3332,6 +3329,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("TEXT"); + b.Property("Exclusions") + .HasColumnType("TEXT"); + b.Property("Favorites") .HasColumnType("TEXT"); diff --git a/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.cs b/util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.cs similarity index 77% rename from util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.cs rename to util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.cs index c1a6cf33fa01..a953ba961264 100644 --- a/util/SqliteMigrations/Migrations/20261026215402_AddCipherBrowserHealthExclusions.cs +++ b/util/SqliteMigrations/Migrations/20261103214017_AddCipherExclusions.cs @@ -5,13 +5,13 @@ namespace Bit.SqliteMigrations.Migrations; /// -public partial class AddCipherBrowserHealthExclusions : Migration +public partial class AddCipherExclusions : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher", type: "TEXT", nullable: true); @@ -21,7 +21,7 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( - name: "BrowserHealthExclusions", + name: "Exclusions", table: "Cipher"); } } diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 135b97a3e72b..10057628ca7a 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -3317,9 +3317,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Attachments") .HasColumnType("TEXT"); - b.Property("BrowserHealthExclusions") - .HasColumnType("TEXT"); - b.Property("CreationDate") .HasColumnType("TEXT"); @@ -3329,6 +3326,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeletedDate") .HasColumnType("TEXT"); + b.Property("Exclusions") + .HasColumnType("TEXT"); + b.Property("Favorites") .HasColumnType("TEXT"); From 8ad269a3e9ec88a2bbad77c83cb44c4f2f4aa3de Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 3 Nov 2026 16:44:41 -0500 Subject: [PATCH 22/27] [PM-41931] refactor(dirt): Return the user's exclusions as exclusions Rename the cipher response property to Exclusions, so the JSON field is exclusions, as the tech breakdown and the SDK cipher model expect. --- src/Api/Vault/Models/Response/CipherResponseModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Api/Vault/Models/Response/CipherResponseModel.cs b/src/Api/Vault/Models/Response/CipherResponseModel.cs index e20a85019eb4..10e3b4f6c916 100644 --- a/src/Api/Vault/Models/Response/CipherResponseModel.cs +++ b/src/Api/Vault/Models/Response/CipherResponseModel.cs @@ -296,7 +296,7 @@ protected CipherResponseModel( Favorite = cipher.Favorite; Edit = cipher.Edit; ArchivedDate = cipher.ArchivedDate; - ExcludedRiskCategories = cipher.ExcludedRiskCategories == null + Exclusions = cipher.ExcludedRiskCategories == null ? null : JsonSerializer.Deserialize>(cipher.ExcludedRiskCategories); ViewPassword = cipher.ViewPassword; @@ -315,7 +315,7 @@ public static CipherResponseModel From(FullCipherAccess access, CipherDetails ci public bool Edit { get; set; } public bool ViewPassword { get; set; } public DateTime? ArchivedDate { get; set; } - public IEnumerable? ExcludedRiskCategories { get; set; } + public IEnumerable? Exclusions { get; set; } public CipherPermissionsResponseModel Permissions { get; set; } } From 7bbf30bf50ce4cd21800b08419a48172afa8ce8a Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 3 Nov 2026 16:45:58 -0500 Subject: [PATCH 23/27] [PM-41931] test(dirt): Cover an exclude with no cipher ids --- .../CipherBrowserHealthExclusionRepositoryTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index 6783311a7989..6d2031009c24 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -28,6 +28,15 @@ public async Task ExcludeAsync_PersonalLogin_ReturnsOneAndStoresRiskCategory( Assert.Equal(["weak"], RiskCategories(details)); } + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_EmptyIds_ReturnsZero( + ICipherBrowserHealthExclusionRepository sut, IUserRepository userRepository) + { + var user = await userRepository.CreateTestUserAsync(); + + Assert.Equal(0, await sut.ExcludeAsync([], user.Id, "weak")); + } + [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, From a8848326b7c736d05016684cfbb05495ed835276 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Nov 2026 11:13:10 -0500 Subject: [PATCH 24/27] [PM-41931] fix(dirt): Match the risk category exactly on SQL Server The duplicate check used the database's case-insensitive collation, so SQL Server treated "Weak" as a duplicate of "weak" while EF stored both. Compare with Latin1_General_BIN2 so both tracks match the exact text. --- .../Cipher_UpdateManyExclusions.sql | 2 +- .../CipherBrowserHealthExclusionRepositoryTests.cs | 13 +++++++++++++ .../DbScripts/2026-10-27_00_AddCipherExclusions.sql | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql index ee0108345528..7ed705a25889 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql @@ -21,7 +21,7 @@ BEGIN WHEN @Exclude = 1 AND EXISTS ( SELECT 1 FROM OPENJSON(C.[Exclusions], CONCAT('$."', @UserId, '"')) - WHERE [value] = @RiskCategory + WHERE [value] COLLATE Latin1_General_BIN2 = @RiskCategory -- exact match, like EF ) THEN C.[Exclusions] WHEN @Exclude = 1 THEN JSON_MODIFY( C.[Exclusions], diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs index 6d2031009c24..37b17707afa5 100644 --- a/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/CipherBrowserHealthExclusionRepositoryTests.cs @@ -37,6 +37,19 @@ public async Task ExcludeAsync_EmptyIds_ReturnsZero( Assert.Equal(0, await sut.ExcludeAsync([], user.Id, "weak")); } + [DatabaseTheory, DatabaseData] + public async Task ExcludeAsync_SameRiskCategoryDifferentCase_StoresBoth( + ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await userRepository.CreateTestUserAsync(); + var cipher = await CreateCipherAsync(cipherRepository, user.Id); + await sut.ExcludeAsync([cipher.Id], user.Id, "weak"); + + await sut.ExcludeAsync([cipher.Id], user.Id, "Weak"); + + Assert.Equal(["weak", "Weak"], RiskCategories(await cipherRepository.GetByIdAsync(cipher.Id, user.Id))); + } + [DatabaseTheory, DatabaseData] public async Task ExcludeAsync_OrganizationCipher_ReturnsZero( ICipherBrowserHealthExclusionRepository sut, ICipherRepository cipherRepository, IUserRepository userRepository, diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql index f3db9f1fe853..29079757c0e3 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql @@ -548,7 +548,7 @@ BEGIN WHEN @Exclude = 1 AND EXISTS ( SELECT 1 FROM OPENJSON(C.[Exclusions], CONCAT('$."', @UserId, '"')) - WHERE [value] = @RiskCategory + WHERE [value] COLLATE Latin1_General_BIN2 = @RiskCategory -- exact match, like EF ) THEN C.[Exclusions] WHEN @Exclude = 1 THEN JSON_MODIFY( C.[Exclusions], From e3e4216fc757ffbbda17faddece20326be3cf837 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Nov 2026 16:50:41 -0500 Subject: [PATCH 25/27] [PM-41931] refactor(dirt): Rename the exclusions read parameter --- .../Repositories/Queries/UserCipherDetailsQuery.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index 2f275ef7ece1..b84255c799d9 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -147,13 +147,13 @@ from cg in cg_g.DefaultIfEmpty() } - internal static string GetExcludedRiskCategories(Guid? userId, string browserHealthExclusions) + internal static string GetExcludedRiskCategories(Guid? userId, string exclusionsJson) { try { - if (userId.HasValue && !string.IsNullOrWhiteSpace(browserHealthExclusions)) + if (userId.HasValue && !string.IsNullOrWhiteSpace(exclusionsJson)) { - var exclusions = JsonSerializer.Deserialize>>(browserHealthExclusions); + var exclusions = JsonSerializer.Deserialize>>(exclusionsJson); if (exclusions.TryGetValue(userId.Value, out var riskCategories)) { return JsonSerializer.Serialize(riskCategories); From 8fcc1615207f1418f9b0c9a86e6a611d32c2f8af Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Nov 2026 17:08:22 -0500 Subject: [PATCH 26/27] [PM-41931] refactor(dirt): Accept the risk category as NVARCHAR VARCHAR turns non-Latin letters into "?", while EF stores the full text. NVARCHAR(50) keeps any category the API sends, matching the NVARCHAR(MAX) column it is written to. --- .../dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql | 2 +- util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql index 7ed705a25889..e325f2cf42ce 100644 --- a/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql +++ b/src/Sql/dbo/Dirt/Stored Procedures/Cipher_UpdateManyExclusions.sql @@ -2,7 +2,7 @@ CREATE PROCEDURE [dbo].[Cipher_UpdateManyExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, @Exclude AS BIT, - @RiskCategory AS VARCHAR(50) = NULL + @RiskCategory AS NVARCHAR(50) = NULL AS BEGIN SET NOCOUNT ON diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql index 29079757c0e3..af24ae1ebc46 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql +++ b/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql @@ -529,7 +529,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateManyExclusions] @Ids AS [dbo].[GuidIdArray] READONLY, @UserId AS UNIQUEIDENTIFIER, @Exclude AS BIT, - @RiskCategory AS VARCHAR(50) = NULL + @RiskCategory AS NVARCHAR(50) = NULL AS BEGIN SET NOCOUNT ON From 0d82e8535a4b31b6b15d34f0774fe1abb4cd55e6 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Nov 2026 15:01:04 -0500 Subject: [PATCH 27/27] [PM-41931] fix(dirt): Run the exclusions migration after the permission-level reads main added 2026-11-02_00_ApplyHighestPermissionLevelInReads.sql, which redefines CipherDetails_ReadByIdUserId without ExcludedRiskCategories. Our 2026-10-27 script ran before it, so a new database lost the column. Re-date the script to 2026-11-05 and copy the current CipherDetails_ReadByIdUserId into it, so it keeps the highest permission level from main and adds ExcludedRiskCategories. --- ...ns.sql => 2026-11-05_00_AddCipherExclusions.sql} | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) rename util/Migrator/DbScripts/{2026-10-27_00_AddCipherExclusions.sql => 2026-11-05_00_AddCipherExclusions.sql} (97%) diff --git a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql b/util/Migrator/DbScripts/2026-11-05_00_AddCipherExclusions.sql similarity index 97% rename from util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql rename to util/Migrator/DbScripts/2026-11-05_00_AddCipherExclusions.sql index af24ae1ebc46..e6a3c1ae6460 100644 --- a/util/Migrator/DbScripts/2026-10-27_00_AddCipherExclusions.sql +++ b/util/Migrator/DbScripts/2026-11-05_00_AddCipherExclusions.sql @@ -498,11 +498,16 @@ SELECT [OrganizationUseTotp], [ArchivedDate], [ExcludedRiskCategories], - MAX ([Edit]) AS [Edit], - MAX ([ViewPassword]) AS [ViewPassword], - MAX ([Manage]) AS [Manage] + -- The highest rank holds Manage as 4, Edit as 2 and ViewPassword as 1; each flag is one part of it + (MAX(R.[Rank]) / 2) % 2 AS [Edit], + MAX(R.[Rank]) % 2 AS [ViewPassword], + MAX(R.[Rank]) / 4 AS [Manage] FROM - [dbo].[UserCipherDetails](@UserId) + [dbo].[UserCipherDetails](@UserId) UCD + -- Rank orders a grant by Manage, then Edit, then ViewPassword, so the highest permission level wins + CROSS APPLY ( + SELECT UCD.[Manage] * 4 + UCD.[Edit] * 2 + UCD.[ViewPassword] AS [Rank] + ) R WHERE [Id] = @Id GROUP BY