From f069fafea1e9cbcd26862d4c14cf1a7ffb5efe4f Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 30 Dec 2025 14:21:38 +1000 Subject: [PATCH 01/41] First pass at semaphore --- .../Entities/DefaultCollectionSemaphore.cs | 8 + .../Repositories/ICollectionRepository.cs | 11 + .../Repositories/CollectionRepository.cs | 84 ++++ ...lectionSemaphoreEntityTypeConfiguration.cs | 31 ++ .../Models/DefaultCollectionSemaphore.cs | 21 + .../Repositories/CollectionRepository.cs | 18 + .../Repositories/DatabaseContext.cs | 3 +- .../Collection_UpsertDefaultCollections.sql | 86 ++++ .../Tables/DefaultCollectionSemaphore.sql | 12 + ...RepositoryUpsertDefaultCollectionsTests.cs | 378 ++++++++++++++++++ ...25-12-30_00_DefaultCollectionSemaphore.sql | 23 ++ ...01_Collection_UpsertDefaultCollections.sql | 83 ++++ ..._02_PopulateDefaultCollectionSemaphore.sql | 29 ++ 13 files changed, 785 insertions(+), 2 deletions(-) create mode 100644 src/Core/Entities/DefaultCollectionSemaphore.cs create mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs create mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs create mode 100644 src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql create mode 100644 src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql create mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs create mode 100644 util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql create mode 100644 util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql create mode 100644 util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql diff --git a/src/Core/Entities/DefaultCollectionSemaphore.cs b/src/Core/Entities/DefaultCollectionSemaphore.cs new file mode 100644 index 000000000000..808227e61e66 --- /dev/null +++ b/src/Core/Entities/DefaultCollectionSemaphore.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Entities; + +public class DefaultCollectionSemaphore +{ + public Guid OrganizationId { get; set; } + public Guid OrganizationUserId { get; set; } + public DateTime CreationDate { get; set; } = DateTime.UtcNow; +} diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index f86147ca7da6..6bf1839bc0a2 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -65,10 +65,21 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col /// /// Creates default user collections for the specified organization users if they do not already have one. + /// Uses the stored procedure approach with semaphore-based duplicate prevention. /// /// The Organization ID. /// The Organization User IDs to create default collections for. /// The encrypted string to use as the default collection name. /// Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); + + /// + /// Creates default user collections for the specified organization users using bulk insert operations. + /// Inserts semaphore entries before collections to prevent duplicates. + /// + /// The Organization ID. + /// The Organization User IDs to create default collections for. + /// The encrypted string to use as the default collection name. + /// + Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); } diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 9985b41d56c4..edc238add64b 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -368,6 +368,29 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } + await using var connection = new SqlConnection(ConnectionString); + await connection.OpenAsync(); + + var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds); + await connection.ExecuteAsync( + "[dbo].[Collection_UpsertDefaultCollections]", + new + { + OrganizationId = organizationId, + DefaultCollectionName = defaultCollectionName, + OrganizationUserIdsJson = organizationUserIdsJson + }, + commandType: CommandType.StoredProcedure); + } + + public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + { + organizationUserIds = organizationUserIds.ToList(); + if (!organizationUserIds.Any()) + { + return; + } + await using var connection = new SqlConnection(ConnectionString); connection.Open(); await using var transaction = connection.BeginTransaction(); @@ -384,6 +407,17 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } + // CRITICAL: Insert semaphore entries BEFORE collections + // TODO: this will result in a creation date of the semaphore AFTER that of the collection, which is weird + var now = DateTime.UtcNow; + var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore + { + OrganizationId = organizationId, + OrganizationUserId = c.OrganizationUserId, + CreationDate = now + }).ToList(); + + await BulkInsertDefaultCollectionSemaphoresAsync(connection, transaction, semaphores); await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); @@ -455,6 +489,56 @@ INNER JOIN return (collectionUsers, collections); } + private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, List semaphores) + { + if (!semaphores.Any()) + { + return; + } + + // Sort by composite key to reduce deadlocks + var sortedSemaphores = semaphores + .OrderBy(s => s.OrganizationId) + .ThenBy(s => s.OrganizationUserId) + .ToList(); + + using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction); + bulkCopy.DestinationTableName = "[dbo].[DefaultCollectionSemaphore]"; + bulkCopy.BatchSize = 500; + bulkCopy.BulkCopyTimeout = 120; + bulkCopy.EnableStreaming = true; + + var dataTable = new DataTable("DefaultCollectionSemaphoreDataTable"); + + var organizationIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationId), typeof(Guid)); + dataTable.Columns.Add(organizationIdColumn); + var organizationUserIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationUserId), typeof(Guid)); + dataTable.Columns.Add(organizationUserIdColumn); + var creationDateColumn = new DataColumn(nameof(DefaultCollectionSemaphore.CreationDate), typeof(DateTime)); + dataTable.Columns.Add(creationDateColumn); + + foreach (DataColumn col in dataTable.Columns) + { + bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName); + } + + var keys = new DataColumn[2]; + keys[0] = organizationIdColumn; + keys[1] = organizationUserIdColumn; + dataTable.PrimaryKey = keys; + + foreach (var semaphore in sortedSemaphores) + { + var row = dataTable.NewRow(); + row[organizationIdColumn] = semaphore.OrganizationId; + row[organizationUserIdColumn] = semaphore.OrganizationUserId; + row[creationDateColumn] = semaphore.CreationDate; + dataTable.Rows.Add(row); + } + + await bulkCopy.WriteToServerAsync(dataTable); + } + public class CollectionWithGroupsAndUsers : Collection { public CollectionWithGroupsAndUsers() { } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs new file mode 100644 index 000000000000..33d59bae4a15 --- /dev/null +++ b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs @@ -0,0 +1,31 @@ +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.AdminConsole.Configurations; + +public class DefaultCollectionSemaphoreEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(dcs => new { dcs.OrganizationId, dcs.OrganizationUserId }); + + // Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) + // Organization FK uses NoAction to avoid competing cascade paths + builder + .HasOne(dcs => dcs.Organization) + .WithMany() + .HasForeignKey(dcs => dcs.OrganizationId) + .OnDelete(DeleteBehavior.NoAction); + + // OrganizationUser FK cascades deletions to ensure automatic cleanup + builder + .HasOne(dcs => dcs.OrganizationUser) + .WithMany() + .HasForeignKey(dcs => dcs.OrganizationUserId) + .OnDelete(DeleteBehavior.Cascade); + + builder.ToTable(nameof(DefaultCollectionSemaphore)); + } +} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs new file mode 100644 index 000000000000..e900b36d15be --- /dev/null +++ b/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.Models; + +namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +public class DefaultCollectionSemaphore : Core.Entities.DefaultCollectionSemaphore +{ + public virtual Organization? Organization { get; set; } + public virtual OrganizationUser? OrganizationUser { get; set; } +} + +public class DefaultCollectionSemaphoreMapperProfile : Profile +{ + public DefaultCollectionSemaphoreMapperProfile() + { + CreateMap() + .ForMember(dcs => dcs.Organization, opt => opt.Ignore()) + .ForMember(dcs => dcs.OrganizationUser, opt => opt.Ignore()) + .ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 5aa156d1f821..c5a501c82755 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -3,6 +3,7 @@ using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Utilities; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using LinqToDB.EntityFrameworkCore; @@ -815,12 +816,29 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } + // CRITICAL: Insert semaphore entries BEFORE collections + // TODO: this will result in a creation date of the semaphore AFTER that of the collection, which is weird + var now = DateTime.UtcNow; + var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore + { + OrganizationId = organizationId, + OrganizationUserId = c.OrganizationUserId, + CreationDate = now + }).ToList(); + + await dbContext.BulkCopyAsync(semaphores); await dbContext.BulkCopyAsync(collections); await dbContext.BulkCopyAsync(collectionUsers); await dbContext.SaveChangesAsync(); } + public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + { + // EF uses the same bulk copy approach as the main method + await UpsertDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); + } + private async Task> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) { var results = await dbContext.OrganizationUsers diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index b748a26db2df..f48bbc6c24f9 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -17,8 +17,6 @@ using DP = Microsoft.AspNetCore.DataProtection; -#nullable enable - namespace Bit.Infrastructure.EntityFramework.Repositories; public class DatabaseContext : DbContext @@ -45,6 +43,7 @@ public DatabaseContext(DbContextOptions options) public DbSet CollectionCiphers { get; set; } public DbSet CollectionGroups { get; set; } public DbSet CollectionUsers { get; set; } + public DbSet DefaultCollectionSemaphores { get; set; } public DbSet Devices { get; set; } public DbSet EmergencyAccesses { get; set; } public DbSet Events { get; set; } diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql new file mode 100644 index 000000000000..b0adce75c126 --- /dev/null +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql @@ -0,0 +1,86 @@ +-- Creates default user collections for organization users +-- Uses semaphore table to prevent duplicate default collections at database level +-- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) +-- Organization FK uses NoAction to avoid competing cascade paths +CREATE PROCEDURE [dbo].[Collection_UpsertDefaultCollections] + @OrganizationId UNIQUEIDENTIFIER, + @DefaultCollectionName VARCHAR(MAX), + @OrganizationUserIdsJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse JSON once into table variable with pre-generated collection IDs + DECLARE @OrganizationUserIds TABLE + ( + [OrganizationUserId] UNIQUEIDENTIFIER, + [CollectionId] UNIQUEIDENTIFIER + ); + + INSERT INTO @OrganizationUserIds + ( + [OrganizationUserId], + [CollectionId] + ) + SELECT + CAST([value] AS UNIQUEIDENTIFIER), + NEWID() + FROM + OPENJSON(@OrganizationUserIdsJson); + + -- Insert semaphore entries first to obtain the "lock" + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationId], + [OrganizationUserId], + [CreationDate] + ) + SELECT + @OrganizationId, + ou.[OrganizationUserId], + GETUTCDATE() + FROM + @OrganizationUserIds ou; + + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ou.[CollectionId], + @OrganizationId, + @DefaultCollectionName, + GETUTCDATE(), + GETUTCDATE(), + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserIds ou; + + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ou.[CollectionId], + ou.[OrganizationUserId], + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserIds ou; +END diff --git a/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql b/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql new file mode 100644 index 000000000000..34f81f237c80 --- /dev/null +++ b/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql @@ -0,0 +1,12 @@ +-- Semaphore table to prevent duplicate default collections per organization user +-- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) +-- OrganizationId FK has NO ACTION to avoid competing cascade paths +CREATE TABLE [dbo].[DefaultCollectionSemaphore] +( + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationId] ASC, [OrganizationUserId] ASC), + CONSTRAINT [FK_DefaultCollectionSemaphore_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), -- NO ACTION to avoid competing cascades + CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE -- Cascades from OrganizationUser deletion +); diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs new file mode 100644 index 000000000000..54ae9cb19d27 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs @@ -0,0 +1,378 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Enums; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; + +public class CollectionRepositoryUpsertDefaultCollectionsTests +{ + /// + /// Test that UpsertDefaultCollectionsAsync successfully creates default collections for new users + /// + [DatabaseTheory, DatabaseData] + public async Task UpsertDefaultCollectionsAsync_CreatesDefaultCollections_Success( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var user1 = await userRepository.CreateAsync(new User + { + Name = "Test User 1", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var user2 = await userRepository.CreateAsync(new User + { + Name = "Test User 2", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user1.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user2.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + // Act + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser1.Id, orgUser2.Id }, + "My Items"); + + // Assert + var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); + var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); + + Assert.Equal(2, defaultCollections.Count); + Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Name)); + Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.OrganizationId)); + } + + /// + /// Test that calling UpsertDefaultCollectionsAsync multiple times does NOT create duplicates + /// + [DatabaseTheory, DatabaseData] + public async Task UpsertDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreateDuplicates( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + // Act - Call twice + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items"); + + // Second call should not create duplicate + await Assert.ThrowsAnyAsync(() => + collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items")); + + // Assert - Only one collection should exist + var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); + var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); + + Assert.Single(defaultCollections); + } + + /// + /// Test that UpsertDefaultCollectionsBulkAsync creates semaphores before collections + /// + [DatabaseTheory, DatabaseData] + public async Task UpsertDefaultCollectionsBulkAsync_CreatesSemaphoresBeforeCollections_Success( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository, + DatabaseContext databaseContext) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + // Act + await collectionRepository.UpsertDefaultCollectionsBulkAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items"); + + // Assert - Verify semaphore was created + var semaphore = await databaseContext.DefaultCollectionSemaphores + .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id && s.OrganizationUserId == orgUser.Id); + + Assert.NotNull(semaphore); + Assert.Equal(organization.Id, semaphore.OrganizationId); + Assert.Equal(orgUser.Id, semaphore.OrganizationUserId); + + // Verify collection was created + var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); + var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); + + Assert.Single(defaultCollections); + } + + /// + /// Test that deleting an OrganizationUser cascades to DefaultCollectionSemaphore + /// + [DatabaseTheory, DatabaseData] + public async Task DeleteOrganizationUser_CascadesToSemaphore_Success( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository, + DatabaseContext databaseContext) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items"); + + // Verify semaphore exists + var semaphoreBefore = await databaseContext.DefaultCollectionSemaphores + .FirstOrDefaultAsync(s => s.OrganizationUserId == orgUser.Id); + Assert.NotNull(semaphoreBefore); + + // Act - Delete organization user + await organizationUserRepository.DeleteAsync(orgUser); + + // Assert - Semaphore should be cascade deleted + var semaphoreAfter = await databaseContext.DefaultCollectionSemaphores + .FirstOrDefaultAsync(s => s.OrganizationUserId == orgUser.Id); + Assert.Null(semaphoreAfter); + } + + /// + /// Test that deleting an Organization cascades through OrganizationUser to DefaultCollectionSemaphore + /// Note: Cascade path is Organization -> OrganizationUser -> DefaultCollectionSemaphore (not direct) + /// + [DatabaseTheory, DatabaseData] + public async Task DeleteOrganization_CascadesThroughOrganizationUser_Success( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository, + DatabaseContext databaseContext) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items"); + + // Verify semaphore exists + var semaphoreBefore = await databaseContext.DefaultCollectionSemaphores + .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id); + Assert.NotNull(semaphoreBefore); + + // Act - Delete organization (which cascades to OrganizationUser, which cascades to semaphore) + await organizationRepository.DeleteAsync(organization); + + // Assert - Semaphore should be cascade deleted via OrganizationUser + var semaphoreAfter = await databaseContext.DefaultCollectionSemaphores + .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id); + Assert.Null(semaphoreAfter); + } + + /// + /// Test that UpsertDefaultCollectionsAsync with empty user list does nothing + /// + [DatabaseTheory, DatabaseData] + public async Task UpsertDefaultCollectionsAsync_WithEmptyList_DoesNothing( + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository) + { + // Arrange + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + // Act + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + Array.Empty(), + "My Items"); + + // Assert - No collections should be created + var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); + Assert.Empty(collections); + } + + /// + /// Test that UpsertDefaultCollectionsAsync creates CollectionUser entries with correct permissions + /// + [DatabaseTheory, DatabaseData] + public async Task UpsertDefaultCollectionsAsync_CreatesCollectionUsersWithCorrectPermissions( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "billing@email.com" + }); + + var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + }); + + // Act + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + new[] { orgUser.Id }, + "My Items"); + + // Assert + var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); + var defaultCollection = collections.First(c => c.Type == CollectionType.DefaultUserCollection); + + var collectionUsers = await collectionRepository.GetManyUsersByIdAsync(defaultCollection.Id); + var collectionUser = collectionUsers.Single(); + + Assert.Equal(orgUser.Id, collectionUser.Id); + Assert.False(collectionUser.ReadOnly); + Assert.False(collectionUser.HidePasswords); + Assert.True(collectionUser.Manage); + } +} diff --git a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql new file mode 100644 index 000000000000..421c6e23885b --- /dev/null +++ b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql @@ -0,0 +1,23 @@ +-- Create DefaultCollectionSemaphore table +-- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) +-- OrganizationId FK has NO ACTION to avoid competing cascade paths +IF OBJECT_ID('[dbo].[DefaultCollectionSemaphore]') IS NULL +BEGIN + CREATE TABLE [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, + [CreationDate] DATETIME2(7) NOT NULL, + CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED + ( + [OrganizationId] ASC, + [OrganizationUserId] ASC + ), + CONSTRAINT [FK_DefaultCollectionSemaphore_Organization] FOREIGN KEY ([OrganizationId]) + REFERENCES [dbo].[Organization] ([Id]), -- NO ACTION to avoid competing cascades + CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) + REFERENCES [dbo].[OrganizationUser] ([Id]) + ON DELETE CASCADE -- Cascades from OrganizationUser deletion + ); +END +GO diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql new file mode 100644 index 000000000000..e77392044230 --- /dev/null +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql @@ -0,0 +1,83 @@ +CREATE OR ALTER PROCEDURE [dbo].[Collection_UpsertDefaultCollections] + @OrganizationId UNIQUEIDENTIFIER, + @DefaultCollectionName VARCHAR(MAX), + @OrganizationUserIdsJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse JSON once into table variable with pre-generated collection IDs + DECLARE @OrganizationUserIds TABLE + ( + [OrganizationUserId] UNIQUEIDENTIFIER, + [CollectionId] UNIQUEIDENTIFIER + ); + + INSERT INTO @OrganizationUserIds + ( + [OrganizationUserId], + [CollectionId] + ) + SELECT + CAST([value] AS UNIQUEIDENTIFIER), + NEWID() + FROM + OPENJSON(@OrganizationUserIdsJson); + + -- Insert semaphore entries first to obtain the "lock" + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationId], + [OrganizationUserId], + [CreationDate] + ) + SELECT + @OrganizationId, + ou.[OrganizationUserId], + GETUTCDATE() + FROM + @OrganizationUserIds ou; + + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ou.[CollectionId], + @OrganizationId, + @DefaultCollectionName, + GETUTCDATE(), + GETUTCDATE(), + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserIds ou; + + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ou.[CollectionId], + ou.[OrganizationUserId], + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserIds ou; +END +GO diff --git a/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql new file mode 100644 index 000000000000..488377f0df4d --- /dev/null +++ b/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql @@ -0,0 +1,29 @@ +-- Populate DefaultCollectionSemaphore from existing Type=1 (DefaultUserCollection) collections +-- This migration is idempotent and can be run multiple times safely +INSERT INTO [dbo].[DefaultCollectionSemaphore] +( + [OrganizationId], + [OrganizationUserId], + [CreationDate] +) +SELECT DISTINCT + c.[OrganizationId], + cu.[OrganizationUserId], + c.[CreationDate] +FROM + [dbo].[Collection] c +INNER JOIN + [dbo].[CollectionUser] cu ON c.[Id] = cu.[CollectionId] +WHERE + c.[Type] = 1 -- CollectionType.DefaultUserCollection + AND NOT EXISTS + ( + SELECT + 1 + FROM + [dbo].[DefaultCollectionSemaphore] dcs + WHERE + dcs.[OrganizationId] = c.[OrganizationId] + AND dcs.[OrganizationUserId] = cu.[OrganizationUserId] + ); +GO From a91f0967f4094762c4d7bafaaee4fe5a65a02577 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 30 Dec 2025 14:48:49 +1000 Subject: [PATCH 02/41] Make sqlbulkcopy check constraints --- src/Infrastructure.Dapper/Repositories/CollectionRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index edc238add64b..9a528ea44746 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -502,7 +502,7 @@ private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection conn .ThenBy(s => s.OrganizationUserId) .ToList(); - using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction); + using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity & SqlBulkCopyOptions.CheckConstraints, transaction); bulkCopy.DestinationTableName = "[dbo].[DefaultCollectionSemaphore]"; bulkCopy.BatchSize = 500; bulkCopy.BulkCopyTimeout = 120; From 31eebe22857961fa51c11746052dfb73bea0b893 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 12:57:30 +1000 Subject: [PATCH 03/41] Update existing reads to use semaphore --- .../Repositories/CollectionRepository.cs | 11 +++-------- .../Repositories/CollectionRepository.cs | 17 ++--------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 9a528ea44746..36551fb7913c 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -434,16 +434,11 @@ private async Task> GetOrgUserIdsWithDefaultCollectionAsync(SqlCon { const string sql = @" SELECT - ou.Id AS OrganizationUserId + OrganizationUserId FROM - OrganizationUser ou - INNER JOIN - CollectionUser cu ON cu.OrganizationUserId = ou.Id - INNER JOIN - Collection c ON c.Id = cu.CollectionId + [DefaultCollectionSemaphore] dcs WHERE - ou.OrganizationId = @OrganizationId - AND c.Type = @CollectionType; + OrganizationId = @OrganizationId "; var organizationUserIds = await connection.QueryAsync( diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index c5a501c82755..583b6abcdce2 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -841,22 +841,9 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer private async Task> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) { - var results = await dbContext.OrganizationUsers + var results = await dbContext.DefaultCollectionSemaphores .Where(ou => ou.OrganizationId == organizationId) - .Join( - dbContext.CollectionUsers, - ou => ou.Id, - cu => cu.OrganizationUserId, - (ou, cu) => new { ou, cu } - ) - .Join( - dbContext.Collections, - temp => temp.cu.CollectionId, - c => c.Id, - (temp, c) => new { temp.ou, Collection = c } - ) - .Where(x => x.Collection.Type == CollectionType.DefaultUserCollection) - .Select(x => x.ou.Id) + .Select(x => x.OrganizationUserId) .ToListAsync(); return results.ToHashSet(); From e67727bf25a44db42699d2835539ea545d6b6ac3 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:04:04 +1000 Subject: [PATCH 04/41] Update naming in existing bulk tests --- ...s.cs => UpsertDefaultCollectionsBulkTests.cs} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/{UpsertDefaultCollectionsTests.cs => UpsertDefaultCollectionsBulkTests.cs} (86%) diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs similarity index 86% rename from test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs rename to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs index 64dffa473f1f..4eea17c0952c 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs @@ -6,10 +6,10 @@ namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; -public class UpsertDefaultCollectionsTests +public class UpsertDefaultCollectionsBulkTests { [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection( + public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -28,7 +28,7 @@ public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_Wh var defaultCollectionName = $"default-name-{organization.Id}"; // Act - await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); @@ -37,7 +37,7 @@ public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_Wh } [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_ShouldUpsertCreateDefaultCollection_ForUsersWithAndWithoutDefaultCollectionsExist( + public async Task UpsertDefaultCollectionsBulkAsync_ShouldUpsertCreateDefaultCollection_ForUsersWithAndWithoutDefaultCollectionsExist( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -66,7 +66,7 @@ await CreateUserForOrgAsync(userRepository, organizationUserRepository, organiza var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id); // Act - await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, arrangedOrganizationUsers, organization.Id); @@ -75,7 +75,7 @@ await CreateUserForOrgAsync(userRepository, organizationUserRepository, organiza } [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_ShouldNotCreateDefaultCollection_WhenUsersAlreadyHaveOne( + public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollection_WhenUsersAlreadyHaveOne( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -96,7 +96,7 @@ public async Task UpsertDefaultCollectionsAsync_ShouldNotCreateDefaultCollection await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); // Act - await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); @@ -108,7 +108,7 @@ private static async Task CreateUsersWithExistingDefaultCollectionsAsync(ICollec Guid organizationId, IEnumerable affectedOrgUserIds, string defaultCollectionName, OrganizationUser[] resultOrganizationUsers) { - await collectionRepository.UpsertDefaultCollectionsAsync(organizationId, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.UpsertDefaultCollectionsBulkAsync(organizationId, affectedOrgUserIds, defaultCollectionName); await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organizationId); } From ae9d18ac9bac8e9ac06fe73b4184e662d359a691 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:28:45 +1000 Subject: [PATCH 05/41] Add read sproc for semaphores --- src/Core/Repositories/ICollectionRepository.cs | 7 +++++++ .../Repositories/CollectionRepository.cs | 13 +++++++++++++ .../Repositories/CollectionRepository.cs | 13 +++++++++++++ ...tCollectionSemaphore_ReadByOrganizationId.sql | 13 +++++++++++++ .../2025-12-30_00_DefaultCollectionSemaphore.sql | 16 ++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index 6bf1839bc0a2..d64d4f3632a5 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -82,4 +82,11 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col /// The encrypted string to use as the default collection name. /// Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); + + /// + /// Gets organization user IDs that have default collection semaphore entries for the specified organization. + /// + /// The Organization ID. + /// Collection of organization user IDs that have default collection semaphores. + Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId); } diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 36551fb7913c..584ace3e737c 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -430,6 +430,19 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer } } + public async Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[DefaultCollectionSemaphore_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + private async Task> GetOrgUserIdsWithDefaultCollectionAsync(SqlConnection connection, SqlTransaction transaction, Guid organizationId) { const string sql = @" diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 583b6abcdce2..dc94619df3ce 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -839,6 +839,19 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer await UpsertDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } + public async Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + var organizationUserIds = await dbContext.DefaultCollectionSemaphores + .Where(s => s.OrganizationId == organizationId) + .Select(s => s.OrganizationUserId) + .ToListAsync(); + + return organizationUserIds; + } + private async Task> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) { var results = await dbContext.DefaultCollectionSemaphores diff --git a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql new file mode 100644 index 000000000000..f5a8b4d9c31d --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + [OrganizationUserId] + FROM + [dbo].[DefaultCollectionSemaphore] + WHERE + [OrganizationId] = @OrganizationId +END diff --git a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql index 421c6e23885b..e60fe1072d80 100644 --- a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql +++ b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql @@ -21,3 +21,19 @@ BEGIN ); END GO + +-- Create stored procedure to read semaphores by organization +CREATE OR ALTER PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + [OrganizationUserId] + FROM + [dbo].[DefaultCollectionSemaphore] + WHERE + [OrganizationId] = @OrganizationId +END +GO From a53bb23c23ef1ceca9de081838ff3cc06326c02a Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:29:11 +1000 Subject: [PATCH 06/41] Add tests for cascade delete behavior --- .../DefaultCollectionSemaphoreTests.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs new file mode 100644 index 000000000000..38dd28b3670e --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs @@ -0,0 +1,78 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Enums; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; + +/// +/// Tests for DefaultCollectionSemaphore table behavior including cascade deletions +/// +public class DefaultCollectionSemaphoreTests +{ + [Theory, DatabaseData] + public async Task DeleteOrganizationUser_CascadeDeletesSemaphore( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); + + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + [orgUser.Id], + "My Items"); + + // Verify semaphore exists + var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Single(semaphoreBefore, s => s == orgUser.Id); + + // Act - Delete organization user + await organizationUserRepository.DeleteAsync(orgUser); + + // Assert - Semaphore should be cascade deleted + var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Empty(semaphoreAfter); + } + + /// + /// Test that deleting an Organization cascades through OrganizationUser to DefaultCollectionSemaphore + /// Note: Cascade path is Organization -> OrganizationUser -> DefaultCollectionSemaphore (not direct) + /// + [Theory, DatabaseData] + public async Task DeleteOrganization_CascadeDeletesSemaphore_ThroughOrganizationUser( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository, + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); + + await collectionRepository.UpsertDefaultCollectionsAsync( + organization.Id, + [orgUser.Id], + "My Items"); + + // Verify semaphore exists + var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Single(semaphoreBefore, s => s == orgUser.Id); + + // Act - Delete organization (which cascades to OrganizationUser, which cascades to semaphore) + await organizationRepository.DeleteAsync(organization); + + // Assert - Semaphore should be cascade deleted via OrganizationUser + var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Empty(semaphoreAfter); + } +} From 9cc89eb8506324ecf1e799299cf70c21012078a1 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:35:40 +1000 Subject: [PATCH 07/41] Update existing bulk tests to assert semaphores --- .../UpsertDefaultCollectionsBulkTests.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs index 4eea17c0952c..075ff41f9197 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs @@ -23,8 +23,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) ); - - var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id); + var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); var defaultCollectionName = $"default-name-{organization.Id}"; // Act @@ -32,6 +31,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } @@ -54,22 +54,22 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldUpsertCreateDefaultCol var arrangedOrgUserIds = arrangedOrganizationUsers.Select(organizationUser => organizationUser.Id); var defaultCollectionName = $"default-name-{organization.Id}"; - await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, arrangedOrgUserIds, defaultCollectionName, arrangedOrganizationUsers); - var newOrganizationUsers = new List() + var newOrganizationUsers = new List { await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) }; var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers); - var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id); + var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); // Act await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, arrangedOrganizationUsers, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); } @@ -89,10 +89,9 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollec CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) ); - var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id); + var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); var defaultCollectionName = $"default-name-{organization.Id}"; - await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); // Act @@ -100,6 +99,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollec // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } @@ -131,13 +131,19 @@ private static async Task AssertAllUsersHaveOneDefaultCollectionAsync(ICollectio private static async Task CreateUserForOrgAsync(IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, Organization organization) { - var user = await userRepository.CreateTestUserAsync(); var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); return orgUser; } + private static async Task AssertSempahoresCreatedAsync(ICollectionRepository collectionRepository, + IEnumerable organizationUserIds, Guid organizationId) + { + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organizationId); + Assert.Equal(organizationUserIds.ToHashSet(), semaphores.ToHashSet()); + } + private static async Task CleanupAsync(IOrganizationRepository organizationRepository, IUserRepository userRepository, Organization organization, From 843fd7ac9d7545d60c3086444a159ba1fa7235f2 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:41:55 +1000 Subject: [PATCH 08/41] Fix validator using wrong method --- ...rganizationDataOwnershipPolicyValidator.cs | 2 +- ...zationDataOwnershipPolicyValidatorTests.cs | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs index 0bee2a55af19..18bf62da6528 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs @@ -71,7 +71,7 @@ private async Task UpsertDefaultCollectionsForUsersAsync(PolicyUpdate policyUpda return; } - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.UpsertDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, userOrgIds, defaultCollectionName); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs index e6677c8a2379..0c59bbe0cf30 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs @@ -40,7 +40,7 @@ public async Task ExecuteSideEffectsAsync_FeatureFlagDisabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -66,7 +66,7 @@ public async Task ExecuteSideEffectsAsync_PolicyAlreadyEnabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -92,7 +92,7 @@ public async Task ExecuteSideEffectsAsync_PolicyBeingDisabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -118,7 +118,7 @@ public async Task ExecuteSideEffectsAsync_WhenNoUsersExist_DoNothing( // Assert await collectionRepository .DidNotReceive() - .UpsertDefaultCollectionsAsync( + .UpsertDefaultCollectionsBulkAsync( Arg.Any(), Arg.Any>(), Arg.Any()); @@ -207,7 +207,7 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo // Assert await collectionRepository .Received(1) - .UpsertDefaultCollectionsAsync( + .UpsertDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, Arg.Is>(ids => ids.Count() == 3), _defaultUserCollectionName); @@ -246,7 +246,7 @@ public async Task ExecuteSideEffectsAsync_WhenDefaultCollectionNameIsInvalid_Doe // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } private static IPolicyRepository ArrangePolicyRepository(IEnumerable policyDetails) @@ -294,7 +294,7 @@ public async Task ExecutePostUpsertSideEffectAsync_FeatureFlagDisabled_DoesNothi // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsAsync(default, default, default); + .UpsertDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -320,7 +320,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNoth // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsAsync(default, default, default); + .UpsertDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -346,7 +346,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothi // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsAsync(default, default, default); + .UpsertDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -372,7 +372,7 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenNoUsersExist_DoNothing( // Assert await collectionRepository .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsAsync( + .UpsertDefaultCollectionsBulkAsync( default, default, default); @@ -412,7 +412,7 @@ public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsert // Assert await collectionRepository .Received(1) - .UpsertDefaultCollectionsAsync( + .UpsertDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, Arg.Is>(ids => ids.Count() == 3), _defaultUserCollectionName); @@ -444,6 +444,6 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsIn // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsAsync(default, default, default); + .UpsertDefaultCollectionsBulkAsync(default, default, default); } } From 90f2e2baeb003692dff540388eef732938274f7c Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 13:42:32 +1000 Subject: [PATCH 09/41] Rename upsert -> create to reflect behavior --- .../ConfirmOrganizationUserCommand.cs | 2 +- .../Repositories/ICollectionRepository.cs | 10 ++++---- .../Repositories/CollectionRepository.cs | 2 +- .../Repositories/CollectionRepository.cs | 4 ++-- .../ConfirmOrganizationUserCommandTests.cs | 4 ++-- ...ts.cs => CreateDefaultCollectionsTests.cs} | 24 +++++++++---------- .../DefaultCollectionSemaphoreTests.cs | 4 ++-- 7 files changed, 24 insertions(+), 26 deletions(-) rename test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/{CollectionRepositoryUpsertDefaultCollectionsTests.cs => CreateDefaultCollectionsTests.cs} (94%) diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs index b6b49e93e9dc..ed65655477ca 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs @@ -347,6 +347,6 @@ private async Task CreateManyDefaultCollectionsAsync(Guid organizationId, return; } - await _collectionRepository.UpsertDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName); + await _collectionRepository.CreateDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName); } } diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index d64d4f3632a5..80a55087ea90 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -64,23 +64,21 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col IEnumerable users, IEnumerable groups); /// - /// Creates default user collections for the specified organization users if they do not already have one. - /// Uses the stored procedure approach with semaphore-based duplicate prevention. + /// Creates default user collections for the specified organization users. + /// Throws an exception if any user already has a default collection for the organization. /// /// The Organization ID. /// The Organization User IDs to create default collections for. /// The encrypted string to use as the default collection name. - /// - Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); + Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); /// /// Creates default user collections for the specified organization users using bulk insert operations. - /// Inserts semaphore entries before collections to prevent duplicates. + /// Gracefully skips users who already have a default collection for the organization. /// /// The Organization ID. /// The Organization User IDs to create default collections for. /// The encrypted string to use as the default collection name. - /// Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); /// diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 584ace3e737c..be7f9deda7c9 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -360,7 +360,7 @@ public async Task> GetManyUsersByIdAsync( } } - public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { organizationUserIds = organizationUserIds.ToList(); if (!organizationUserIds.Any()) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index dc94619df3ce..8453b99dd6e0 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -795,7 +795,7 @@ private static async Task ReplaceCollectionUsersAsync(DatabaseContext dbContext, // SaveChangesAsync is expected to be called outside this method } - public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { organizationUserIds = organizationUserIds.ToList(); if (!organizationUserIds.Any()) @@ -836,7 +836,7 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { // EF uses the same bulk copy approach as the main method - await UpsertDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); + await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } public async Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs index 5528ecb2a25a..3d5aafbaf3b9 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs @@ -525,7 +525,7 @@ public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganiza await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -560,7 +560,7 @@ public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganiza await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs similarity index 94% rename from test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs rename to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 54ae9cb19d27..ee97655562da 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CollectionRepositoryUpsertDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -10,10 +10,10 @@ namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; -public class CollectionRepositoryUpsertDefaultCollectionsTests +public class CreateDefaultCollectionsTests { /// - /// Test that UpsertDefaultCollectionsAsync successfully creates default collections for new users + /// Test that CreateDefaultCollectionsAsync successfully creates default collections for new users /// [DatabaseTheory, DatabaseData] public async Task UpsertDefaultCollectionsAsync_CreatesDefaultCollections_Success( @@ -62,7 +62,7 @@ public async Task UpsertDefaultCollectionsAsync_CreatesDefaultCollections_Succes }); // Act - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser1.Id, orgUser2.Id }, "My Items"); @@ -77,7 +77,7 @@ await collectionRepository.UpsertDefaultCollectionsAsync( } /// - /// Test that calling UpsertDefaultCollectionsAsync multiple times does NOT create duplicates + /// Test that calling CreateDefaultCollectionsAsync multiple times does NOT create duplicates /// [DatabaseTheory, DatabaseData] public async Task UpsertDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreateDuplicates( @@ -111,14 +111,14 @@ public async Task UpsertDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreat }); // Act - Call twice - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser.Id }, "My Items"); // Second call should not create duplicate await Assert.ThrowsAnyAsync(() => - collectionRepository.UpsertDefaultCollectionsAsync( + collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser.Id }, "My Items")); @@ -221,7 +221,7 @@ public async Task DeleteOrganizationUser_CascadesToSemaphore_Success( Status = OrganizationUserStatusType.Confirmed, }); - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser.Id }, "My Items"); @@ -276,7 +276,7 @@ public async Task DeleteOrganization_CascadesThroughOrganizationUser_Success( Status = OrganizationUserStatusType.Confirmed, }); - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser.Id }, "My Items"); @@ -296,7 +296,7 @@ await collectionRepository.UpsertDefaultCollectionsAsync( } /// - /// Test that UpsertDefaultCollectionsAsync with empty user list does nothing + /// Test that CreateDefaultCollectionsAsync with empty user list does nothing /// [DatabaseTheory, DatabaseData] public async Task UpsertDefaultCollectionsAsync_WithEmptyList_DoesNothing( @@ -313,7 +313,7 @@ public async Task UpsertDefaultCollectionsAsync_WithEmptyList_DoesNothing( }); // Act - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, Array.Empty(), "My Items"); @@ -324,7 +324,7 @@ await collectionRepository.UpsertDefaultCollectionsAsync( } /// - /// Test that UpsertDefaultCollectionsAsync creates CollectionUser entries with correct permissions + /// Test that CreateDefaultCollectionsAsync creates CollectionUser entries with correct permissions /// [DatabaseTheory, DatabaseData] public async Task UpsertDefaultCollectionsAsync_CreatesCollectionUsersWithCorrectPermissions( @@ -358,7 +358,7 @@ public async Task UpsertDefaultCollectionsAsync_CreatesCollectionUsersWithCorrec }); // Act - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, new[] { orgUser.Id }, "My Items"); diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs index 38dd28b3670e..234fa9ab3562 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs @@ -26,7 +26,7 @@ public async Task DeleteOrganizationUser_CascadeDeletesSemaphore( var organization = await organizationRepository.CreateTestOrganizationAsync(); var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, [orgUser.Id], "My Items"); @@ -59,7 +59,7 @@ public async Task DeleteOrganization_CascadeDeletesSemaphore_ThroughOrganization var organization = await organizationRepository.CreateTestOrganizationAsync(); var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - await collectionRepository.UpsertDefaultCollectionsAsync( + await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, [orgUser.Id], "My Items"); From 7aef3da8553f027064faacb54679eaaeba289af8 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 14:04:05 +1000 Subject: [PATCH 10/41] Refine tests (failing) --- .../CreateDefaultCollectionsTests.cs | 373 +++--------------- 1 file changed, 51 insertions(+), 322 deletions(-) diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index ee97655562da..a549aea80e45 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -1,11 +1,5 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Billing.Enums; -using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; using Xunit; namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; @@ -14,113 +8,87 @@ public class CreateDefaultCollectionsTests { /// /// Test that CreateDefaultCollectionsAsync successfully creates default collections for new users + /// with correct permissions /// - [DatabaseTheory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_CreatesDefaultCollections_Success( + [Theory, DatabaseData] + public async Task CreateDefaultCollectionsAsync_CreatesDefaultCollections_Success( IUserRepository userRepository, IOrganizationRepository organizationRepository, ICollectionRepository collectionRepository, IOrganizationUserRepository organizationUserRepository) { // Arrange - var user1 = await userRepository.CreateAsync(new User - { - Name = "Test User 1", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var user2 = await userRepository.CreateAsync(new User - { - Name = "Test User 2", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user1.Id, - Status = OrganizationUserStatusType.Confirmed, - }); - - var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user2.Id, - Status = OrganizationUserStatusType.Confirmed, - }); + var user1 = await userRepository.CreateTestUserAsync("user1"); + var user2 = await userRepository.CreateTestUserAsync("user2"); + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user2); // Act await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, - new[] { orgUser1.Id, orgUser2.Id }, + [orgUser1.Id, orgUser2.Id], "My Items"); // Assert - var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); - var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); + var collectionsWithAccess = await collectionRepository.GetManyByOrganizationIdWithAccessAsync(organization.Id); + var defaultCollections = collectionsWithAccess + .Where(c => c.Item1.Type == CollectionType.DefaultUserCollection) + .ToList(); Assert.Equal(2, defaultCollections.Count); - Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Name)); - Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.OrganizationId)); + Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Item1.Name)); + Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.Item1.OrganizationId)); + + // Verify each user has exactly 1 collection with correct permissions + var orgUser1Collection = Assert.Single(defaultCollections, + c => c.Item2.Users.FirstOrDefault()?.Id == orgUser1.Id); + + Assert.Empty(orgUser1Collection.Item2.Groups); + + var orgUser1CollectionUser = orgUser1Collection.Item2.Users.Single(); + Assert.False(orgUser1CollectionUser.ReadOnly); + Assert.False(orgUser1CollectionUser.HidePasswords); + Assert.True(orgUser1CollectionUser.Manage); + + // Verify each user has exactly 1 collection with correct permissions + var orgUser2Collection = Assert.Single(defaultCollections, + c => c.Item2.Users.FirstOrDefault()?.Id == orgUser2.Id); + + Assert.Empty(orgUser2Collection.Item2.Groups); + + var orgUser2CollectionUser = orgUser2Collection.Item2.Users.Single(); + Assert.False(orgUser2CollectionUser.ReadOnly); + Assert.False(orgUser2CollectionUser.HidePasswords); + Assert.True(orgUser2CollectionUser.Manage); } /// /// Test that calling CreateDefaultCollectionsAsync multiple times does NOT create duplicates /// - [DatabaseTheory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreateDuplicates( + [Theory, DatabaseData] + public async Task CreateDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreateDuplicates( IUserRepository userRepository, IOrganizationRepository organizationRepository, ICollectionRepository collectionRepository, IOrganizationUserRepository organizationUserRepository) { // Arrange - var user = await userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user.Id, - Status = OrganizationUserStatusType.Confirmed, - }); + var user = await userRepository.CreateTestUserAsync(); + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); // Act - Call twice await collectionRepository.CreateDefaultCollectionsAsync( organization.Id, - new[] { orgUser.Id }, + [orgUser.Id], "My Items"); - // Second call should not create duplicate + // Second call should throw and should not create duplicate await Assert.ThrowsAnyAsync(() => collectionRepository.CreateDefaultCollectionsAsync( organization.Id, - new[] { orgUser.Id }, + [orgUser.Id], "My Items")); // Assert - Only one collection should exist @@ -128,251 +96,12 @@ await Assert.ThrowsAnyAsync(() => var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); Assert.Single(defaultCollections); - } - - /// - /// Test that UpsertDefaultCollectionsBulkAsync creates semaphores before collections - /// - [DatabaseTheory, DatabaseData] - public async Task UpsertDefaultCollectionsBulkAsync_CreatesSemaphoresBeforeCollections_Success( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository, - DatabaseContext databaseContext) - { - // Arrange - var user = await userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user.Id, - Status = OrganizationUserStatusType.Confirmed, - }); - - // Act - await collectionRepository.UpsertDefaultCollectionsBulkAsync( - organization.Id, - new[] { orgUser.Id }, - "My Items"); - - // Assert - Verify semaphore was created - var semaphore = await databaseContext.DefaultCollectionSemaphores - .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id && s.OrganizationUserId == orgUser.Id); - - Assert.NotNull(semaphore); - Assert.Equal(organization.Id, semaphore.OrganizationId); - Assert.Equal(orgUser.Id, semaphore.OrganizationUserId); - - // Verify collection was created - var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); - var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); - - Assert.Single(defaultCollections); - } - - /// - /// Test that deleting an OrganizationUser cascades to DefaultCollectionSemaphore - /// - [DatabaseTheory, DatabaseData] - public async Task DeleteOrganizationUser_CascadesToSemaphore_Success( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository, - DatabaseContext databaseContext) - { - // Arrange - var user = await userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user.Id, - Status = OrganizationUserStatusType.Confirmed, - }); - - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - new[] { orgUser.Id }, - "My Items"); - - // Verify semaphore exists - var semaphoreBefore = await databaseContext.DefaultCollectionSemaphores - .FirstOrDefaultAsync(s => s.OrganizationUserId == orgUser.Id); - Assert.NotNull(semaphoreBefore); - - // Act - Delete organization user - await organizationUserRepository.DeleteAsync(orgUser); - - // Assert - Semaphore should be cascade deleted - var semaphoreAfter = await databaseContext.DefaultCollectionSemaphores - .FirstOrDefaultAsync(s => s.OrganizationUserId == orgUser.Id); - Assert.Null(semaphoreAfter); - } - - /// - /// Test that deleting an Organization cascades through OrganizationUser to DefaultCollectionSemaphore - /// Note: Cascade path is Organization -> OrganizationUser -> DefaultCollectionSemaphore (not direct) - /// - [DatabaseTheory, DatabaseData] - public async Task DeleteOrganization_CascadesThroughOrganizationUser_Success( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository, - DatabaseContext databaseContext) - { - // Arrange - var user = await userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user.Id, - Status = OrganizationUserStatusType.Confirmed, - }); - - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - new[] { orgUser.Id }, - "My Items"); - - // Verify semaphore exists - var semaphoreBefore = await databaseContext.DefaultCollectionSemaphores - .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id); - Assert.NotNull(semaphoreBefore); - - // Act - Delete organization (which cascades to OrganizationUser, which cascades to semaphore) - await organizationRepository.DeleteAsync(organization); - - // Assert - Semaphore should be cascade deleted via OrganizationUser - var semaphoreAfter = await databaseContext.DefaultCollectionSemaphores - .FirstOrDefaultAsync(s => s.OrganizationId == organization.Id); - Assert.Null(semaphoreAfter); - } - - /// - /// Test that CreateDefaultCollectionsAsync with empty user list does nothing - /// - [DatabaseTheory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_WithEmptyList_DoesNothing( - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository) - { - // Arrange - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - // Act - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - Array.Empty(), - "My Items"); - - // Assert - No collections should be created - var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); - Assert.Empty(collections); - } - - /// - /// Test that CreateDefaultCollectionsAsync creates CollectionUser entries with correct permissions - /// - [DatabaseTheory, DatabaseData] - public async Task UpsertDefaultCollectionsAsync_CreatesCollectionUsersWithCorrectPermissions( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository) - { - // Arrange - var user = await userRepository.CreateAsync(new User - { - Name = "Test User", - Email = $"test+{Guid.NewGuid()}@email.com", - ApiKey = "TEST", - SecurityStamp = "stamp", - }); - - var organization = await organizationRepository.CreateAsync(new Organization - { - Name = "Test Org", - PlanType = PlanType.EnterpriseAnnually, - Plan = "Test Plan", - BillingEmail = "billing@email.com" - }); - - var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser - { - OrganizationId = organization.Id, - UserId = user.Id, - Status = OrganizationUserStatusType.Confirmed, - }); - - // Act - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - new[] { orgUser.Id }, - "My Items"); - - // Assert - var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); - var defaultCollection = collections.First(c => c.Type == CollectionType.DefaultUserCollection); - - var collectionUsers = await collectionRepository.GetManyUsersByIdAsync(defaultCollection.Id); - var collectionUser = collectionUsers.Single(); - Assert.Equal(orgUser.Id, collectionUser.Id); - Assert.False(collectionUser.ReadOnly); - Assert.False(collectionUser.HidePasswords); - Assert.True(collectionUser.Manage); + var access = await collectionRepository.GetManyUsersByIdAsync(defaultCollections.Single().Id); + var userAccess = Assert.Single(access); + Assert.Equal(orgUser.Id, userAccess.Id); + Assert.False(userAccess.ReadOnly); + Assert.False(userAccess.HidePasswords); + Assert.True(userAccess.Manage); } } From 53bc10015fd020ef84371b3ee83813441cd8fb5d Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 14:06:39 +1000 Subject: [PATCH 11/41] Assert semaphores in Create tests --- .../CollectionRepository/CreateDefaultCollectionsTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index a549aea80e45..5f81035b0c4a 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -40,6 +40,9 @@ await collectionRepository.CreateDefaultCollectionsAsync( Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Item1.Name)); Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.Item1.OrganizationId)); + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Equal([orgUser1.Id, orgUser2.Id], semaphores.ToHashSet()); + // Verify each user has exactly 1 collection with correct permissions var orgUser1Collection = Assert.Single(defaultCollections, c => c.Item2.Users.FirstOrDefault()?.Id == orgUser1.Id); @@ -97,6 +100,9 @@ await Assert.ThrowsAnyAsync(() => Assert.Single(defaultCollections); + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + Assert.Equal([orgUser.Id], semaphores); + var access = await collectionRepository.GetManyUsersByIdAsync(defaultCollections.Single().Id); var userAccess = Assert.Single(access); Assert.Equal(orgUser.Id, userAccess.Id); From 36aad90cb8e9f1973615375fe688f541210ea954 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 14:25:14 +1000 Subject: [PATCH 12/41] Explicit transaction for mssql --- .../Collection_UpsertDefaultCollections.sql | 119 ++++++++++-------- .../CreateDefaultCollectionsTests.cs | 2 +- ...01_Collection_UpsertDefaultCollections.sql | 119 ++++++++++-------- 3 files changed, 133 insertions(+), 107 deletions(-) diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql index b0adce75c126..6c6be401dfa6 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql @@ -28,59 +28,72 @@ BEGIN FROM OPENJSON(@OrganizationUserIdsJson); - -- Insert semaphore entries first to obtain the "lock" - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationId], - [OrganizationUserId], - [CreationDate] - ) - SELECT - @OrganizationId, - ou.[OrganizationUserId], - GETUTCDATE() - FROM - @OrganizationUserIds ou; + BEGIN TRANSACTION; - -- Insert collections for users who obtained semaphore entries - INSERT INTO [dbo].[Collection] - ( - [Id], - [OrganizationId], - [Name], - [CreationDate], - [RevisionDate], - [Type], - [ExternalId], - [DefaultUserCollectionEmail] - ) - SELECT - ou.[CollectionId], - @OrganizationId, - @DefaultCollectionName, - GETUTCDATE(), - GETUTCDATE(), - 1, -- CollectionType.DefaultUserCollection - NULL, - NULL - FROM - @OrganizationUserIds ou; + BEGIN TRY + -- Insert semaphore entries first to obtain the "lock" + -- If this fails due to duplicate key, the entire transaction will be rolled back + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationId], + [OrganizationUserId], + [CreationDate] + ) + SELECT + @OrganizationId, + ou.[OrganizationUserId], + GETUTCDATE() + FROM + @OrganizationUserIds ou; - -- Insert collection user mappings - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - ou.[CollectionId], - ou.[OrganizationUserId], - 0, -- ReadOnly = false - 0, -- HidePasswords = false - 1 -- Manage = true - FROM - @OrganizationUserIds ou; + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ou.[CollectionId], + @OrganizationId, + @DefaultCollectionName, + GETUTCDATE(), + GETUTCDATE(), + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserIds ou; + + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ou.[CollectionId], + ou.[OrganizationUserId], + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserIds ou; + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRANSACTION; + + THROW; + END CATCH END diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 5f81035b0c4a..852a11e0bcf8 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -92,7 +92,7 @@ await Assert.ThrowsAnyAsync(() => collectionRepository.CreateDefaultCollectionsAsync( organization.Id, [orgUser.Id], - "My Items")); + "My Items Duplicate")); // Assert - Only one collection should exist var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql index e77392044230..55e727712ce3 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql @@ -24,60 +24,73 @@ BEGIN FROM OPENJSON(@OrganizationUserIdsJson); - -- Insert semaphore entries first to obtain the "lock" - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationId], - [OrganizationUserId], - [CreationDate] - ) - SELECT - @OrganizationId, - ou.[OrganizationUserId], - GETUTCDATE() - FROM - @OrganizationUserIds ou; + BEGIN TRANSACTION; - -- Insert collections for users who obtained semaphore entries - INSERT INTO [dbo].[Collection] - ( - [Id], - [OrganizationId], - [Name], - [CreationDate], - [RevisionDate], - [Type], - [ExternalId], - [DefaultUserCollectionEmail] - ) - SELECT - ou.[CollectionId], - @OrganizationId, - @DefaultCollectionName, - GETUTCDATE(), - GETUTCDATE(), - 1, -- CollectionType.DefaultUserCollection - NULL, - NULL - FROM - @OrganizationUserIds ou; + BEGIN TRY + -- Insert semaphore entries first to obtain the "lock" + -- If this fails due to duplicate key, the entire transaction will be rolled back + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationId], + [OrganizationUserId], + [CreationDate] + ) + SELECT + @OrganizationId, + ou.[OrganizationUserId], + GETUTCDATE() + FROM + @OrganizationUserIds ou; - -- Insert collection user mappings - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - ou.[CollectionId], - ou.[OrganizationUserId], - 0, -- ReadOnly = false - 0, -- HidePasswords = false - 1 -- Manage = true - FROM - @OrganizationUserIds ou; + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ou.[CollectionId], + @OrganizationId, + @DefaultCollectionName, + GETUTCDATE(), + GETUTCDATE(), + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserIds ou; + + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ou.[CollectionId], + ou.[OrganizationUserId], + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserIds ou; + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRANSACTION; + + THROW; + END CATCH END GO From f668a0ce0afa5454841a8c0c27241ce7cf80763f Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Wed, 31 Dec 2025 14:26:27 +1000 Subject: [PATCH 13/41] Fix sproc name --- src/Infrastructure.Dapper/Repositories/CollectionRepository.cs | 2 +- .../Stored Procedures/Collection_UpsertDefaultCollections.sql | 2 +- .../2025-12-30_01_Collection_UpsertDefaultCollections.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index be7f9deda7c9..c94bff4e85fe 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -373,7 +373,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds); await connection.ExecuteAsync( - "[dbo].[Collection_UpsertDefaultCollections]", + "[dbo].[Collection_CreateDefaultCollections]", new { OrganizationId = organizationId, diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql index 6c6be401dfa6..9885ac6ef43e 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql @@ -2,7 +2,7 @@ -- Uses semaphore table to prevent duplicate default collections at database level -- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) -- Organization FK uses NoAction to avoid competing cascade paths -CREATE PROCEDURE [dbo].[Collection_UpsertDefaultCollections] +CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @OrganizationUserIdsJson NVARCHAR(MAX) diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql index 55e727712ce3..8c9cab9dafb1 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql @@ -1,4 +1,4 @@ -CREATE OR ALTER PROCEDURE [dbo].[Collection_UpsertDefaultCollections] +CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @OrganizationUserIdsJson NVARCHAR(MAX) From 7ea237f5d55fa466f7c81a376f0e8d77d993dc8a Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 09:30:24 +1000 Subject: [PATCH 14/41] Remove redundant OrganizationId column; remove private read method used by bulk insert --- .../Entities/DefaultCollectionSemaphore.cs | 3 +- .../Repositories/ICollectionRepository.cs | 12 +++- .../Repositories/CollectionRepository.cs | 55 +++++-------------- ...lectionSemaphoreEntityTypeConfiguration.cs | 12 +--- .../Models/DefaultCollectionSemaphore.cs | 4 +- .../Repositories/CollectionRepository.cs | 27 +++------ ...> Collection_CreateDefaultCollections.sql} | 2 - .../Tables/DefaultCollectionSemaphore.sql | 7 +-- ...llectionSemaphore_ReadByOrganizationId.sql | 13 ----- ...ionSemaphore_ReadByOrganizationUserIds.sql | 13 +++++ .../CreateDefaultCollectionsTests.cs | 8 +-- .../DefaultCollectionSemaphoreTests.cs | 16 ++---- .../UpsertDefaultCollectionsBulkTests.cs | 13 +++-- ...25-12-30_00_DefaultCollectionSemaphore.sql | 30 ++++------ ...1_Collection_CreateDefaultCollections.sql} | 2 - 15 files changed, 79 insertions(+), 138 deletions(-) rename src/Sql/dbo/AdminConsole/Stored Procedures/{Collection_UpsertDefaultCollections.sql => Collection_CreateDefaultCollections.sql} (97%) delete mode 100644 src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql create mode 100644 src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql rename util/Migrator/DbScripts/{2025-12-30_01_Collection_UpsertDefaultCollections.sql => 2025-12-30_01_Collection_CreateDefaultCollections.sql} (97%) diff --git a/src/Core/Entities/DefaultCollectionSemaphore.cs b/src/Core/Entities/DefaultCollectionSemaphore.cs index 808227e61e66..ea2568e671eb 100644 --- a/src/Core/Entities/DefaultCollectionSemaphore.cs +++ b/src/Core/Entities/DefaultCollectionSemaphore.cs @@ -1,8 +1,7 @@ -namespace Bit.Core.Entities; +namespace Bit.Core.Entities; public class DefaultCollectionSemaphore { - public Guid OrganizationId { get; set; } public Guid OrganizationUserId { get; set; } public DateTime CreationDate { get; set; } = DateTime.UtcNow; } diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index 80a55087ea90..f51f64a7d86f 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -82,9 +82,15 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); /// - /// Gets organization user IDs that have default collection semaphore entries for the specified organization. + /// Gets default collection semaphores for the given organizationUserIds. + /// If an organizationUserId is missing from the result set, they do not have a semaphore set. /// - /// The Organization ID. + /// The organization User IDs to check semaphores for. /// Collection of organization user IDs that have default collection semaphores. - Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId); + /// + /// The semaphore table is used to ensure that an organizationUser can only have 1 default collection. + /// (That is, a user may only have 1 default collection per organization.) + /// If a semaphore is returned, that user already has a default collection for that organization. + /// + Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds); } diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index c94bff4e85fe..df82a0c158fc 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -391,15 +391,15 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer return; } + var orgUserIdWithDefaultCollection = await GetDefaultCollectionSemaphoresAsync(organizationUserIds); + var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); + await using var connection = new SqlConnection(ConnectionString); connection.Open(); await using var transaction = connection.BeginTransaction(); + try { - var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(connection, transaction, organizationId); - - var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); if (!collectionUsers.Any() || !collections.Any()) @@ -412,7 +412,6 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer var now = DateTime.UtcNow; var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore { - OrganizationId = organizationId, OrganizationUserId = c.OrganizationUserId, CreationDate = now }).ToList(); @@ -430,37 +429,16 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer } } - public async Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId) + public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds) { - using (var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - "[dbo].[DefaultCollectionSemaphore_ReadByOrganizationId]", - new { OrganizationId = organizationId }, - commandType: CommandType.StoredProcedure); + await using var connection = new SqlConnection(ConnectionString); - return results.ToList(); - } - } + var results = await connection.QueryAsync( + "[dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds]", + new { OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); - private async Task> GetOrgUserIdsWithDefaultCollectionAsync(SqlConnection connection, SqlTransaction transaction, Guid organizationId) - { - const string sql = @" - SELECT - OrganizationUserId - FROM - [DefaultCollectionSemaphore] dcs - WHERE - OrganizationId = @OrganizationId - "; - - var organizationUserIds = await connection.QueryAsync( - sql, - new { OrganizationId = organizationId, CollectionType = CollectionType.DefaultUserCollection }, - transaction: transaction - ); - - return organizationUserIds.ToHashSet(); + return results.ToHashSet(); } private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName) @@ -506,8 +484,7 @@ private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection conn // Sort by composite key to reduce deadlocks var sortedSemaphores = semaphores - .OrderBy(s => s.OrganizationId) - .ThenBy(s => s.OrganizationUserId) + .OrderBy(s => s.OrganizationUserId) .ToList(); using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity & SqlBulkCopyOptions.CheckConstraints, transaction); @@ -518,8 +495,6 @@ private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection conn var dataTable = new DataTable("DefaultCollectionSemaphoreDataTable"); - var organizationIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationId), typeof(Guid)); - dataTable.Columns.Add(organizationIdColumn); var organizationUserIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationUserId), typeof(Guid)); dataTable.Columns.Add(organizationUserIdColumn); var creationDateColumn = new DataColumn(nameof(DefaultCollectionSemaphore.CreationDate), typeof(DateTime)); @@ -530,15 +505,13 @@ private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection conn bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName); } - var keys = new DataColumn[2]; - keys[0] = organizationIdColumn; - keys[1] = organizationUserIdColumn; + var keys = new DataColumn[1]; + keys[0] = organizationUserIdColumn; dataTable.PrimaryKey = keys; foreach (var semaphore in sortedSemaphores) { var row = dataTable.NewRow(); - row[organizationIdColumn] = semaphore.OrganizationId; row[organizationUserIdColumn] = semaphore.OrganizationUserId; row[creationDateColumn] = semaphore.CreationDate; dataTable.Rows.Add(row); diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs index 33d59bae4a15..389294111157 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs @@ -1,4 +1,4 @@ -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -9,15 +9,7 @@ public class DefaultCollectionSemaphoreEntityTypeConfiguration : IEntityTypeConf public void Configure(EntityTypeBuilder builder) { builder - .HasKey(dcs => new { dcs.OrganizationId, dcs.OrganizationUserId }); - - // Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) - // Organization FK uses NoAction to avoid competing cascade paths - builder - .HasOne(dcs => dcs.Organization) - .WithMany() - .HasForeignKey(dcs => dcs.OrganizationId) - .OnDelete(DeleteBehavior.NoAction); + .HasKey(dcs => new { dcs.OrganizationUserId }); // OrganizationUser FK cascades deletions to ensure automatic cleanup builder diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs index e900b36d15be..6d51bb9f219c 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs @@ -1,11 +1,10 @@ -using AutoMapper; +using AutoMapper; using Bit.Infrastructure.EntityFramework.Models; namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; public class DefaultCollectionSemaphore : Core.Entities.DefaultCollectionSemaphore { - public virtual Organization? Organization { get; set; } public virtual OrganizationUser? OrganizationUser { get; set; } } @@ -14,7 +13,6 @@ public class DefaultCollectionSemaphoreMapperProfile : Profile public DefaultCollectionSemaphoreMapperProfile() { CreateMap() - .ForMember(dcs => dcs.Organization, opt => opt.Ignore()) .ForMember(dcs => dcs.OrganizationUser, opt => opt.Ignore()) .ReverseMap(); } diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 8453b99dd6e0..d152a67afb84 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -803,12 +803,12 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } + var orgUserIdWithDefaultCollection = await GetDefaultCollectionSemaphoresAsync(organizationUserIds); + var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); + using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(dbContext, organizationId); - var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); if (!collectionUsers.Any() || !collections.Any()) @@ -821,7 +821,6 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable var now = DateTime.UtcNow; var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore { - OrganizationId = organizationId, OrganizationUserId = c.OrganizationUserId, CreationDate = now }).ToList(); @@ -839,27 +838,19 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } - public async Task> GetDefaultCollectionSemaphoresAsync(Guid organizationId) + public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds) { + var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); + using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - var organizationUserIds = await dbContext.DefaultCollectionSemaphores - .Where(s => s.OrganizationId == organizationId) + var result = await dbContext.DefaultCollectionSemaphores + .Where(s => organizationUserIdsHashSet.Contains(s.OrganizationUserId)) .Select(s => s.OrganizationUserId) .ToListAsync(); - return organizationUserIds; - } - - private async Task> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) - { - var results = await dbContext.DefaultCollectionSemaphores - .Where(ou => ou.OrganizationId == organizationId) - .Select(x => x.OrganizationUserId) - .ToListAsync(); - - return results.ToHashSet(); + return result.ToHashSet(); } private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName) diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql similarity index 97% rename from src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql rename to src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql index 9885ac6ef43e..98c6ee3b1890 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_UpsertDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql @@ -35,12 +35,10 @@ BEGIN -- If this fails due to duplicate key, the entire transaction will be rolled back INSERT INTO [dbo].[DefaultCollectionSemaphore] ( - [OrganizationId], [OrganizationUserId], [CreationDate] ) SELECT - @OrganizationId, ou.[OrganizationUserId], GETUTCDATE() FROM diff --git a/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql b/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql index 34f81f237c80..6161781c8cc4 100644 --- a/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql +++ b/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql @@ -3,10 +3,9 @@ -- OrganizationId FK has NO ACTION to avoid competing cascade paths CREATE TABLE [dbo].[DefaultCollectionSemaphore] ( - [OrganizationId] UNIQUEIDENTIFIER NOT NULL, [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL, - CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationId] ASC, [OrganizationUserId] ASC), - CONSTRAINT [FK_DefaultCollectionSemaphore_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), -- NO ACTION to avoid competing cascades - CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE -- Cascades from OrganizationUser deletion + CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationUserId] ASC), + CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) + REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE ); diff --git a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql deleted file mode 100644 index f5a8b4d9c31d..000000000000 --- a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationId.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationId] - @OrganizationId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - [OrganizationUserId] - FROM - [dbo].[DefaultCollectionSemaphore] - WHERE - [OrganizationId] = @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql new file mode 100644 index 000000000000..b1bfae98b689 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds] + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + SELECT + [OrganizationUserId] + FROM + [dbo].[DefaultCollectionSemaphore] DCS + INNER JOIN + @OrganizationUserIds OU ON [OU].[Id] = [DCS].[OrganizationUserId] +END diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 852a11e0bcf8..b8ca61a3c793 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Repositories; using Xunit; @@ -40,8 +40,8 @@ await collectionRepository.CreateDefaultCollectionsAsync( Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Item1.Name)); Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.Item1.OrganizationId)); - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); - Assert.Equal([orgUser1.Id, orgUser2.Id], semaphores.ToHashSet()); + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser1.Id, orgUser2.Id]); + Assert.Equal([orgUser1.Id, orgUser2.Id], semaphores); // Verify each user has exactly 1 collection with correct permissions var orgUser1Collection = Assert.Single(defaultCollections, @@ -100,7 +100,7 @@ await Assert.ThrowsAnyAsync(() => Assert.Single(defaultCollections); - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); Assert.Equal([orgUser.Id], semaphores); var access = await collectionRepository.GetManyUsersByIdAsync(defaultCollections.Single().Id); diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs index 234fa9ab3562..39cd1d775cfe 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs @@ -1,10 +1,4 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Billing.Enums; -using Bit.Core.Entities; -using Bit.Core.Enums; -using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; +using Bit.Core.Repositories; using Xunit; namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; @@ -32,14 +26,14 @@ await collectionRepository.CreateDefaultCollectionsAsync( "My Items"); // Verify semaphore exists - var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); Assert.Single(semaphoreBefore, s => s == orgUser.Id); // Act - Delete organization user await organizationUserRepository.DeleteAsync(orgUser); // Assert - Semaphore should be cascade deleted - var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); Assert.Empty(semaphoreAfter); } @@ -65,14 +59,14 @@ await collectionRepository.CreateDefaultCollectionsAsync( "My Items"); // Verify semaphore exists - var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); Assert.Single(semaphoreBefore, s => s == orgUser.Id); // Act - Delete organization (which cascades to OrganizationUser, which cascades to semaphore) await organizationRepository.DeleteAsync(organization); // Assert - Semaphore should be cascade deleted via OrganizationUser - var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organization.Id); + var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); Assert.Empty(semaphoreAfter); } } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs index 075ff41f9197..1376465a5bb9 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs @@ -31,7 +31,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } @@ -69,7 +69,7 @@ await CreateUserForOrgAsync(userRepository, organizationUserRepository, organiza // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, arrangedOrganizationUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); } @@ -99,7 +99,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollec // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds, organization.Id); + await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } @@ -138,10 +138,11 @@ private static async Task CreateUserForOrgAsync(IUserRepositor } private static async Task AssertSempahoresCreatedAsync(ICollectionRepository collectionRepository, - IEnumerable organizationUserIds, Guid organizationId) + IEnumerable organizationUserIds) { - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organizationId); - Assert.Equal(organizationUserIds.ToHashSet(), semaphores.ToHashSet()); + var organizationUserIdHashSet = organizationUserIds.ToHashSet(); + var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organizationUserIdHashSet); + Assert.Equal(organizationUserIdHashSet, semaphores); } private static async Task CleanupAsync(IOrganizationRepository organizationRepository, diff --git a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql index e60fe1072d80..285c39e77dbd 100644 --- a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql +++ b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql @@ -5,26 +5,18 @@ IF OBJECT_ID('[dbo].[DefaultCollectionSemaphore]') IS NULL BEGIN CREATE TABLE [dbo].[DefaultCollectionSemaphore] ( - [OrganizationId] UNIQUEIDENTIFIER NOT NULL, - [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, - [CreationDate] DATETIME2(7) NOT NULL, - CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED - ( - [OrganizationId] ASC, - [OrganizationUserId] ASC - ), - CONSTRAINT [FK_DefaultCollectionSemaphore_Organization] FOREIGN KEY ([OrganizationId]) - REFERENCES [dbo].[Organization] ([Id]), -- NO ACTION to avoid competing cascades - CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) - REFERENCES [dbo].[OrganizationUser] ([Id]) - ON DELETE CASCADE -- Cascades from OrganizationUser deletion + [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationUserId] ASC), + CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) + REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE ); END GO --- Create stored procedure to read semaphores by organization -CREATE OR ALTER PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationId] - @OrganizationId UNIQUEIDENTIFIER +-- Create stored procedure to read semaphores by OrganizationUserId +CREATE OR ALTER PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds] + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY AS BEGIN SET NOCOUNT ON @@ -32,8 +24,8 @@ BEGIN SELECT [OrganizationUserId] FROM - [dbo].[DefaultCollectionSemaphore] - WHERE - [OrganizationId] = @OrganizationId + [dbo].[DefaultCollectionSemaphore] DCS + INNER JOIN + @OrganizationUserIds OU ON [OU].[Id] = [DCS].[OrganizationUserId] END GO diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql similarity index 97% rename from util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql rename to util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql index 8c9cab9dafb1..3e8fe14b1a6b 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_UpsertDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql @@ -31,12 +31,10 @@ BEGIN -- If this fails due to duplicate key, the entire transaction will be rolled back INSERT INTO [dbo].[DefaultCollectionSemaphore] ( - [OrganizationId], [OrganizationUserId], [CreationDate] ) SELECT - @OrganizationId, ou.[OrganizationUserId], GETUTCDATE() FROM From a6fcee7094cbf63369f9fd5b976658be63a75822 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 09:37:49 +1000 Subject: [PATCH 15/41] Generate EF migrations --- ...256_DefaultCollectionSemaphore.Designer.cs | 3467 ++++++++++++++++ ...251231233256_DefaultCollectionSemaphore.cs | 41 + .../DatabaseContextModelSnapshot.cs | 24 + ...251_DefaultCollectionSemaphore.Designer.cs | 3473 +++++++++++++++++ ...251231233251_DefaultCollectionSemaphore.cs | 40 + .../DatabaseContextModelSnapshot.cs | 24 + ...301_DefaultCollectionSemaphore.Designer.cs | 3456 ++++++++++++++++ ...251231233301_DefaultCollectionSemaphore.cs | 40 + .../DatabaseContextModelSnapshot.cs | 24 + 9 files changed, 10589 insertions(+) create mode 100644 util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs create mode 100644 util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs create mode 100644 util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs diff --git a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs new file mode 100644 index 000000000000..6d55cbf7653b --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs @@ -0,0 +1,3467 @@ +// +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("20251231233256_DefaultCollectionSemaphore")] + partial class DefaultCollectionSemaphore + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("tinyint(1)"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePhishingBlocker") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("ApplicationCount") + .HasColumnType("int"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalApplicationCount") + .HasColumnType("int"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalMemberCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordCount") + .HasColumnType("int"); + + b.Property("MemberAtRiskCount") + .HasColumnType("int"); + + b.Property("MemberCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("PasswordCount") + .HasColumnType("int"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SecurityState") + .HasColumnType("longtext"); + + b.Property("SecurityVersion") + .HasColumnType("int"); + + b.Property("SignedPublicKey") + .HasColumnType("longtext"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SignatureAlgorithm") + .HasColumnType("tinyint unsigned"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("EditorServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VersionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ArchivedDate") + .HasColumnType("datetime(6)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs new file mode 100644 index 000000000000..b0f1f61dfbae --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + /// + public partial class DefaultCollectionSemaphore : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + CreationDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUser~", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); + } + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 2fef7dd84188..c50e52cf77b9 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -69,6 +69,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2601,6 +2614,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs new file mode 100644 index 000000000000..0f1d0cc9db2a --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs @@ -0,0 +1,3473 @@ +// +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("20251231233251_DefaultCollectionSemaphore")] + partial class DefaultCollectionSemaphore + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("boolean"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePhishingBlocker") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("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"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("ApplicationCount") + .HasColumnType("integer"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalApplicationCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordCount") + .HasColumnType("integer"); + + b.Property("MemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("MemberCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("PasswordCount") + .HasColumnType("integer"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SecurityState") + .HasColumnType("text"); + + b.Property("SecurityVersion") + .HasColumnType("integer"); + + b.Property("SignedPublicKey") + .HasColumnType("text"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SignatureAlgorithm") + .HasColumnType("smallint"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("EditorServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ArchivedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs new file mode 100644 index 000000000000..36defcfa8347 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + /// + public partial class DefaultCollectionSemaphore : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "uuid", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUse~", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); + } + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 09054fbf2a68..347e36b07bfd 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -70,6 +70,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2607,6 +2620,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs new file mode 100644 index 000000000000..1afaf2b4dd88 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs @@ -0,0 +1,3456 @@ +// +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("20251231233301_DefaultCollectionSemaphore")] + partial class DefaultCollectionSemaphore + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("SyncSeats") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePhishingBlocker") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordCount") + .HasColumnType("INTEGER"); + + b.Property("MemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("MemberCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("PasswordCount") + .HasColumnType("INTEGER"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .HasColumnType("INTEGER"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("EditorServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VersionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ArchivedDate") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs new file mode 100644 index 000000000000..4a5e7ec8da80 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations +{ + /// + public partial class DefaultCollectionSemaphore : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); + } + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index d9fa1ff599a6..503f92cd5800 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -64,6 +64,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.HasKey("OrganizationUserId"); + + b.ToTable("DefaultCollectionSemaphore", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2590,6 +2603,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationUser"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") From 21d7276625d1b11e8ad3be34b68a6be0a0f5d0c5 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 13:51:10 +1000 Subject: [PATCH 16/41] Move filtering out of db layer --- ...rganizationDataOwnershipPolicyValidator.cs | 16 ++- .../Repositories/ICollectionRepository.cs | 9 +- .../Repositories/CollectionRepository.cs | 13 +- .../Repositories/CollectionRepository.cs | 14 +- ...zationDataOwnershipPolicyValidatorTests.cs | 127 ++++++++++++++++-- ...s => CreateDefaultCollectionsBulkTests.cs} | 68 ++++++++-- 6 files changed, 196 insertions(+), 51 deletions(-) rename test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/{UpsertDefaultCollectionsBulkTests.cs => CreateDefaultCollectionsBulkTests.cs} (66%) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs index 18bf62da6528..22d9fd20dd06 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs @@ -64,16 +64,26 @@ private async Task UpsertDefaultCollectionsForUsersAsync(PolicyUpdate policyUpda var userOrgIds = requirements .Select(requirement => requirement.GetDefaultCollectionRequestOnPolicyEnable(policyUpdate.OrganizationId)) .Where(request => request.ShouldCreateDefaultCollection) - .Select(request => request.OrganizationUserId); + .Select(request => request.OrganizationUserId) + .ToList(); if (!userOrgIds.Any()) { return; } - await collectionRepository.UpsertDefaultCollectionsBulkAsync( + // Filter out users who already have default collections + var existingSemaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(userOrgIds); + var usersNeedingDefaultCollections = userOrgIds.Except(existingSemaphores).ToList(); + + if (!usersNeedingDefaultCollections.Any()) + { + return; + } + + await collectionRepository.CreateDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, - userOrgIds, + usersNeedingDefaultCollections, defaultCollectionName); } } diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index f51f64a7d86f..91232db05859 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -74,12 +74,17 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col /// /// Creates default user collections for the specified organization users using bulk insert operations. - /// Gracefully skips users who already have a default collection for the organization. + /// Use this if you need to create collections for > ~1k users. + /// Throws an exception if any user already has a default collection for the organization. /// /// The Organization ID. /// The Organization User IDs to create default collections for. /// The encrypted string to use as the default collection name. - Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); + /// + /// If any of the OrganizationUsers may already have default collections, the caller should first filter out these + /// users using GetDefaultCollectionSemaphoresAsync before calling this method. + /// + Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); /// /// Gets default collection semaphores for the given organizationUserIds. diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index df82a0c158fc..ac289eafb9fd 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -383,7 +383,7 @@ await connection.ExecuteAsync( commandType: CommandType.StoredProcedure); } - public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { organizationUserIds = organizationUserIds.ToList(); if (!organizationUserIds.Any()) @@ -391,8 +391,7 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer return; } - var orgUserIdWithDefaultCollection = await GetDefaultCollectionSemaphoresAsync(organizationUserIds); - var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); + var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, organizationUserIds, defaultCollectionName); await using var connection = new SqlConnection(ConnectionString); connection.Open(); @@ -400,15 +399,9 @@ public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumer try { - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); - - if (!collectionUsers.Any() || !collections.Any()) - { - return; - } // CRITICAL: Insert semaphore entries BEFORE collections - // TODO: this will result in a creation date of the semaphore AFTER that of the collection, which is weird + // Database will throw on duplicate primary key (OrganizationUserId) var now = DateTime.UtcNow; var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore { diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index d152a67afb84..1fb387f48ae7 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -803,21 +803,13 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } - var orgUserIdWithDefaultCollection = await GetDefaultCollectionSemaphoresAsync(organizationUserIds); - var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); + var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, organizationUserIds, defaultCollectionName); using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); - - if (!collectionUsers.Any() || !collections.Any()) - { - return; - } - // CRITICAL: Insert semaphore entries BEFORE collections - // TODO: this will result in a creation date of the semaphore AFTER that of the collection, which is weird + // Database will throw on duplicate primary key (OrganizationUserId) var now = DateTime.UtcNow; var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore { @@ -832,7 +824,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await dbContext.SaveChangesAsync(); } - public async Task UpsertDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { // EF uses the same bulk copy approach as the main method await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs index 0c59bbe0cf30..b61888759ab5 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs @@ -40,7 +40,7 @@ public async Task ExecuteSideEffectsAsync_FeatureFlagDisabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -66,7 +66,7 @@ public async Task ExecuteSideEffectsAsync_PolicyAlreadyEnabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -92,7 +92,7 @@ public async Task ExecuteSideEffectsAsync_PolicyBeingDisabled_DoesNothing( // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory, BitAutoData] @@ -118,7 +118,7 @@ public async Task ExecuteSideEffectsAsync_WhenNoUsersExist_DoNothing( // Assert await collectionRepository .DidNotReceive() - .UpsertDefaultCollectionsBulkAsync( + .CreateDefaultCollectionsBulkAsync( Arg.Any(), Arg.Any>(), Arg.Any()); @@ -198,6 +198,11 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); + // Mock GetDefaultCollectionSemaphoresAsync to return empty set (no existing collections) + collectionRepository + .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) + .Returns(new HashSet()); + var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); @@ -207,12 +212,101 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo // Assert await collectionRepository .Received(1) - .UpsertDefaultCollectionsBulkAsync( + .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); + + await collectionRepository + .Received(1) + .CreateDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, Arg.Is>(ids => ids.Count() == 3), _defaultUserCollectionName); } + [Theory] + [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] + public async Task ExecuteSideEffectsAsync_FiltersOutUsersWithExistingCollections( + Policy postUpdatedPolicy, + Policy? previousPolicyState, + [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, + [OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable orgPolicyDetails, + OrganizationDataOwnershipPolicyRequirementFactory factory) + { + // Arrange + var orgPolicyDetailsList = orgPolicyDetails.ToList(); + foreach (var policyDetail in orgPolicyDetailsList) + { + policyDetail.OrganizationId = policyUpdate.OrganizationId; + } + + var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); + var collectionRepository = Substitute.For(); + + // Mock GetDefaultCollectionSemaphoresAsync to return one existing user + var existingUserId = orgPolicyDetailsList[0].OrganizationUserId; + collectionRepository + .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) + .Returns([existingUserId]); + + var sut = ArrangeSut(factory, policyRepository, collectionRepository); + var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert - Should filter out the existing user + await collectionRepository + .Received(1) + .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); + + await collectionRepository + .Received(1) + .CreateDefaultCollectionsBulkAsync( + policyUpdate.OrganizationId, + Arg.Is>(ids => ids.Count() == 2 && !ids.Contains(existingUserId)), + _defaultUserCollectionName); + } + + [Theory] + [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] + public async Task ExecuteSideEffectsAsync_DoesNotCallRepository_WhenAllUsersHaveExistingCollections( + Policy postUpdatedPolicy, + Policy? previousPolicyState, + [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, + [OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable orgPolicyDetails, + OrganizationDataOwnershipPolicyRequirementFactory factory) + { + // Arrange + var orgPolicyDetailsList = orgPolicyDetails.ToList(); + foreach (var policyDetail in orgPolicyDetailsList) + { + policyDetail.OrganizationId = policyUpdate.OrganizationId; + } + + var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); + var collectionRepository = Substitute.For(); + + // Mock GetDefaultCollectionSemaphoresAsync to return all users + var allUserIds = orgPolicyDetailsList.Select(p => p.OrganizationUserId).ToHashSet(); + collectionRepository + .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) + .Returns(allUserIds); + + var sut = ArrangeSut(factory, policyRepository, collectionRepository); + var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert - Should not call CreateDefaultCollectionsBulkAsync when all users already have collections + await collectionRepository + .Received(1) + .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); + + await collectionRepository + .DidNotReceive() + .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + } + private static IEnumerable WhenDefaultCollectionsDoesNotExistTestCases() { yield return [new OrganizationModelOwnershipPolicyModel(null)]; @@ -246,7 +340,7 @@ public async Task ExecuteSideEffectsAsync_WhenDefaultCollectionNameIsInvalid_Doe // Assert await sutProvider.GetDependency() .DidNotReceive() - .UpsertDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } private static IPolicyRepository ArrangePolicyRepository(IEnumerable policyDetails) @@ -294,7 +388,7 @@ public async Task ExecutePostUpsertSideEffectAsync_FeatureFlagDisabled_DoesNothi // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsBulkAsync(default, default, default); + .CreateDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -320,7 +414,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNoth // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsBulkAsync(default, default, default); + .CreateDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -346,7 +440,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothi // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsBulkAsync(default, default, default); + .CreateDefaultCollectionsBulkAsync(default, default, default); } [Theory, BitAutoData] @@ -372,7 +466,7 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenNoUsersExist_DoNothing( // Assert await collectionRepository .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsBulkAsync( + .CreateDefaultCollectionsBulkAsync( default, default, default); @@ -403,6 +497,11 @@ public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsert var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); + // Mock GetDefaultCollectionSemaphoresAsync to return empty set (no existing collections) + collectionRepository + .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) + .Returns(new HashSet()); + var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); @@ -412,7 +511,11 @@ public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsert // Assert await collectionRepository .Received(1) - .UpsertDefaultCollectionsBulkAsync( + .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); + + await collectionRepository + .Received(1) + .CreateDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, Arg.Is>(ids => ids.Count() == 3), _defaultUserCollectionName); @@ -444,6 +547,6 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsIn // Assert await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() - .UpsertDefaultCollectionsBulkAsync(default, default, default); + .CreateDefaultCollectionsBulkAsync(default, default, default); } } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs similarity index 66% rename from test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs rename to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs index 1376465a5bb9..5851d8d468a0 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs @@ -6,10 +6,10 @@ namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; -public class UpsertDefaultCollectionsBulkTests +public class CreateDefaultCollectionsBulkTests { [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection( + public async Task CreateDefaultCollectionsBulkAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -27,7 +27,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio var defaultCollectionName = $"default-name-{organization.Id}"; // Act - await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); @@ -37,7 +37,7 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio } [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsBulkAsync_ShouldUpsertCreateDefaultCollection_ForUsersWithAndWithoutDefaultCollectionsExist( + public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_WhenCallerFiltersExisting( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -64,18 +64,20 @@ await CreateUserForOrgAsync(userRepository, organizationUserRepository, organiza var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers); var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); - // Act - await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + // Act - Caller filters out existing users (new pattern) + var existingSemaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(affectedOrgUserIds); + var usersNeedingCollections = affectedOrgUserIds.Except(existingSemaphores).ToList(); + await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, usersNeedingCollections, defaultCollectionName); - // Assert - await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, arrangedOrganizationUsers, organization.Id); + // Assert - All users now have exactly one collection + await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id); await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); } [Theory, DatabaseData] - public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollection_WhenUsersAlreadyHaveOne( + public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenUsersAlreadyHaveOne( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -94,21 +96,61 @@ public async Task UpsertDefaultCollectionsBulkAsync_ShouldNotCreateDefaultCollec await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); - // Act - await collectionRepository.UpsertDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); + // Act - Try to create again, should throw database constraint exception + await Assert.ThrowsAnyAsync(() => + collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName)); - // Assert + // Assert - Original collections should remain unchanged await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } + [Theory, DatabaseData] + public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenDuplicatesNotFiltered( + IOrganizationRepository organizationRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + + var existingUser = await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization); + var newUser = await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization); + var defaultCollectionName = $"default-name-{organization.Id}"; + + // Create collection for existing user + await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, [existingUser.Id], defaultCollectionName); + + // Act - Try to create for both without filtering (incorrect usage) + await Assert.ThrowsAnyAsync(() => + collectionRepository.CreateDefaultCollectionsBulkAsync( + organization.Id, + [existingUser.Id, newUser.Id], + defaultCollectionName)); + + // Assert - Verify existing user still has collection + var existingUserCollections = await collectionRepository.GetManyByUserIdAsync(existingUser.UserId!.Value); + var existingUserDefaultCollection = existingUserCollections + .SingleOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); + Assert.NotNull(existingUserDefaultCollection); + + // Verify new user does NOT have collection (transaction rolled back) + var newUserCollections = await collectionRepository.GetManyByUserIdAsync(newUser.UserId!.Value); + var newUserDefaultCollection = newUserCollections + .FirstOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); + Assert.Null(newUserDefaultCollection); + + await CleanupAsync(organizationRepository, userRepository, organization, [existingUser, newUser]); + } + private static async Task CreateUsersWithExistingDefaultCollectionsAsync(ICollectionRepository collectionRepository, Guid organizationId, IEnumerable affectedOrgUserIds, string defaultCollectionName, OrganizationUser[] resultOrganizationUsers) { - await collectionRepository.UpsertDefaultCollectionsBulkAsync(organizationId, affectedOrgUserIds, defaultCollectionName); + await collectionRepository.CreateDefaultCollectionsBulkAsync(organizationId, affectedOrgUserIds, defaultCollectionName); await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organizationId); } From 6bcc17f02af6ecbb462338372864b97910741e5c Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 14:12:16 +1000 Subject: [PATCH 17/41] Update existing code paths to use new method --- ...maticallyConfirmOrganizationUserCommand.cs | 19 ++++-------------- .../ConfirmOrganizationUserCommand.cs | 19 ++++-------------- .../AutomaticallyConfirmUsersCommandTests.cs | 20 ++++++------------- .../ConfirmOrganizationUserCommandTests.cs | 13 ++++-------- 4 files changed, 18 insertions(+), 53 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs index 67b5f0da80fa..db2b220cb062 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs @@ -2,9 +2,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; -using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; @@ -79,19 +77,10 @@ private async Task CreateDefaultCollectionsAsync(AutomaticallyConfirmOrganizatio return; } - await collectionRepository.CreateAsync( - new Collection - { - OrganizationId = request.Organization!.Id, - Name = request.DefaultUserCollectionName, - Type = CollectionType.DefaultUserCollection - }, - groups: null, - [new CollectionAccessSelection - { - Id = request.OrganizationUser!.Id, - Manage = true - }]); + await collectionRepository.CreateDefaultCollectionsAsync( + request.Organization!.Id, + [request.OrganizationUser!.Id], + request.DefaultUserCollectionName); } catch (Exception ex) { diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs index ed65655477ca..913d8c40c367 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs @@ -297,21 +297,10 @@ private async Task CreateDefaultCollectionAsync(OrganizationUser organizationUse return; } - var defaultCollection = new Collection - { - OrganizationId = organizationUser.OrganizationId, - Name = defaultUserCollectionName, - Type = CollectionType.DefaultUserCollection - }; - var collectionUser = new CollectionAccessSelection - { - Id = organizationUser.Id, - ReadOnly = false, - HidePasswords = false, - Manage = true - }; - - await _collectionRepository.CreateAsync(defaultCollection, groups: null, users: [collectionUser]); + await _collectionRepository.CreateDefaultCollectionsAsync( + organizationUser.OrganizationId, + [organizationUser.Id], + defaultUserCollectionName); } /// diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs index 1035d5c578bb..48138f97ed1f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs @@ -203,14 +203,10 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectio await sutProvider.GetDependency() .Received(1) - .CreateAsync( - Arg.Is(c => - c.OrganizationId == organization.Id && - c.Name == defaultCollectionName && - c.Type == CollectionType.DefaultUserCollection), - Arg.Is>(groups => groups == null), - Arg.Is>(access => - access.FirstOrDefault(x => x.Id == organizationUser.Id && x.Manage) != null)); + .CreateDefaultCollectionsAsync( + organization.Id, + Arg.Is>(ids => ids.Single() == organizationUser.Id), + defaultCollectionName); } [Theory] @@ -252,9 +248,7 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectio await sutProvider.GetDependency() .DidNotReceive() - .CreateAsync(Arg.Any(), - Arg.Any>(), - Arg.Any>()); + .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Theory] @@ -290,9 +284,7 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WhenCreateDefaultCol var collectionException = new Exception("Collection creation failed"); sutProvider.GetDependency() - .CreateAsync(Arg.Any(), - Arg.Any>(), - Arg.Any>()) + .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any()) .ThrowsAsync(collectionException); // Act diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs index 3d5aafbaf3b9..085f5c2f80e8 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs @@ -494,15 +494,10 @@ public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganiza await sutProvider.GetDependency() .Received(1) - .CreateAsync( - Arg.Is(c => - c.Name == collectionName && - c.OrganizationId == organization.Id && - c.Type == CollectionType.DefaultUserCollection), - Arg.Any>(), - Arg.Is>(cu => - cu.Single().Id == orgUser.Id && - cu.Single().Manage)); + .CreateDefaultCollectionsAsync( + organization.Id, + Arg.Is>(ids => ids.Single() == orgUser.Id), + collectionName); } [Theory, BitAutoData] From 8212af5230688ec7319f556e2684d5e51f96793c Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 14:13:21 +1000 Subject: [PATCH 18/41] dotnet format --- .../ConfirmOrganizationUserCommand.cs | 1 - .../AutomaticallyConfirmUsersCommandTests.cs | 1 - .../ConfirmOrganizationUserCommandTests.cs | 1 - ...251231233256_DefaultCollectionSemaphore.cs | 62 +++++++++---------- ...251231233251_DefaultCollectionSemaphore.cs | 60 +++++++++--------- ...251231233301_DefaultCollectionSemaphore.cs | 60 +++++++++--------- 6 files changed, 88 insertions(+), 97 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs index 913d8c40c367..1a0cdc531ce3 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs @@ -12,7 +12,6 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs index 48138f97ed1f..e219caf0ddb6 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs @@ -9,7 +9,6 @@ using Bit.Core.AdminConsole.Utilities.v2.Validation; using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs index 085f5c2f80e8..071d7313fd4b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs @@ -12,7 +12,6 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Platform.Push; using Bit.Core.Repositories; diff --git a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs index b0f1f61dfbae..d73823066212 100644 --- a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs +++ b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs @@ -1,41 +1,39 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Bit.MySqlMigrations.Migrations +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class DefaultCollectionSemaphore : Migration { /// - public partial class DefaultCollectionSemaphore : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), - CreationDate = table.Column(type: "datetime(6)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUser~", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - } + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + CreationDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUser~", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); } } diff --git a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs index 36defcfa8347..39121813ab8c 100644 --- a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs +++ b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs @@ -1,40 +1,38 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Bit.PostgresMigrations.Migrations +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class DefaultCollectionSemaphore : Migration { /// - public partial class DefaultCollectionSemaphore : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "uuid", nullable: false), - CreationDate = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUse~", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "uuid", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUse~", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); } } diff --git a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs index 4a5e7ec8da80..36db0718fb3e 100644 --- a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs +++ b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs @@ -1,40 +1,38 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Bit.SqliteMigrations.Migrations +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class DefaultCollectionSemaphore : Migration { /// - public partial class DefaultCollectionSemaphore : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "TEXT", nullable: false), - CreationDate = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUserId", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } + migrationBuilder.CreateTable( + name: "DefaultCollectionSemaphore", + columns: table => new + { + OrganizationUserId = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); + table.ForeignKey( + name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUserId", + column: x => x.OrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DefaultCollectionSemaphore"); } } From a9e840988d2f4db5185e886d446c5ae5f8c9e714 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 14:26:31 +1000 Subject: [PATCH 19/41] DRY arrangement code between repositories --- .../Collections/CollectionUtils.cs | 51 +++++++++++++++++++ .../Repositories/CollectionRepository.cs | 38 ++------------ .../Repositories/CollectionRepository.cs | 42 ++------------- 3 files changed, 59 insertions(+), 72 deletions(-) create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs new file mode 100644 index 000000000000..e540c7555559 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs @@ -0,0 +1,51 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Collections; + +public static class CollectionUtils +{ + /// + /// Arranges Collection and CollectionUser objects to create default user collections. + /// + /// The organization ID. + /// The IDs for organization users who need default collections. + /// The encrypted string to use as the default collection name. + /// + public static (IEnumerable collection, IEnumerable collectionUsers) + BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds, + string defaultCollectionName) + { + var collectionUsers = new List(); + var collections = new List(); + + foreach (var orgUserId in organizationUserIds) + { + var collectionId = CoreHelpers.GenerateComb(); + + collections.Add(new Collection + { + Id = collectionId, + OrganizationId = organizationId, + Name = defaultCollectionName, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + Type = CollectionType.DefaultUserCollection, + DefaultUserCollectionEmail = null + + }); + + collectionUsers.Add(new CollectionUser + { + CollectionId = collectionId, + OrganizationUserId = orgUserId, + ReadOnly = false, + HidePasswords = false, + Manage = true, + }); + } + + return (collections, collectionUsers); + } +} diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index ac289eafb9fd..70f2de9d71e2 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -1,6 +1,7 @@ using System.Data; using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -391,7 +392,8 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer return; } - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, organizationUserIds, defaultCollectionName); + var (collections, collectionUsers) = + CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); await using var connection = new SqlConnection(ConnectionString); connection.Open(); @@ -434,40 +436,6 @@ public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable return results.ToHashSet(); } - private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName) - { - var collectionUsers = new List(); - var collections = new List(); - - foreach (var orgUserId in missingDefaultCollectionUserIds) - { - var collectionId = CoreHelpers.GenerateComb(); - - collections.Add(new Collection - { - Id = collectionId, - OrganizationId = organizationId, - Name = defaultCollectionName, - CreationDate = DateTime.UtcNow, - RevisionDate = DateTime.UtcNow, - Type = CollectionType.DefaultUserCollection, - DefaultUserCollectionEmail = null - - }); - - collectionUsers.Add(new CollectionUser - { - CollectionId = collectionId, - OrganizationUserId = orgUserId, - ReadOnly = false, - HidePasswords = false, - Manage = true, - }); - } - - return (collectionUsers, collections); - } - private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, List semaphores) { if (!semaphores.Any()) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 1fb387f48ae7..09ec901aba09 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; @@ -803,7 +804,8 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } - var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, organizationUserIds, defaultCollectionName); + var (collections, collectionUsers) = + CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); @@ -818,8 +820,8 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable }).ToList(); await dbContext.BulkCopyAsync(semaphores); - await dbContext.BulkCopyAsync(collections); - await dbContext.BulkCopyAsync(collectionUsers); + await dbContext.BulkCopyAsync(Mapper.Map>(collections)); + await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); await dbContext.SaveChangesAsync(); } @@ -844,38 +846,4 @@ public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable return result.ToHashSet(); } - - private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName) - { - var collectionUsers = new List(); - var collections = new List(); - - foreach (var orgUserId in missingDefaultCollectionUserIds) - { - var collectionId = CoreHelpers.GenerateComb(); - - collections.Add(new Collection - { - Id = collectionId, - OrganizationId = organizationId, - Name = defaultCollectionName, - CreationDate = DateTime.UtcNow, - RevisionDate = DateTime.UtcNow, - Type = CollectionType.DefaultUserCollection, - DefaultUserCollectionEmail = null - - }); - - collectionUsers.Add(new CollectionUser - { - CollectionId = collectionId, - OrganizationUserId = orgUserId, - ReadOnly = false, - HidePasswords = false, - Manage = true, - }); - } - - return (collectionUsers, collections); - } } From ebbdc946a101e72c41f6f39f07d328ed0495c705 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 15:16:48 +1000 Subject: [PATCH 20/41] Return uniform error --- .../DuplicateDefaultCollectionException.cs | 5 +++ .../Repositories/CollectionRepository.cs | 35 ++++++++++++------ .../Repositories/DatabaseExceptionHelpers.cs | 17 +++++++++ .../Repositories/CollectionRepository.cs | 34 ++++++++++------- .../Repositories/DatabaseExceptionHelpers.cs | 37 +++++++++++++++++++ .../CreateDefaultCollectionsBulkTests.cs | 10 +++-- .../CreateDefaultCollectionsTests.cs | 7 ++-- 7 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs create mode 100644 src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs diff --git a/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs b/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs new file mode 100644 index 000000000000..abe1cdedb085 --- /dev/null +++ b/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs @@ -0,0 +1,5 @@ +namespace Bit.Core.AdminConsole.Collections; + +public class DuplicateDefaultCollectionException() + : Exception("A My Items collection already exists for one or more of the specified organization members."); + diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 70f2de9d71e2..3340fec8f1cc 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -1,13 +1,12 @@ using System.Data; using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; -using Bit.Core.Utilities; using Bit.Infrastructure.Dapper.AdminConsole.Helpers; using Dapper; using Microsoft.Data.SqlClient; @@ -372,16 +371,23 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); - var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds); - await connection.ExecuteAsync( - "[dbo].[Collection_CreateDefaultCollections]", - new - { - OrganizationId = organizationId, - DefaultCollectionName = defaultCollectionName, - OrganizationUserIdsJson = organizationUserIdsJson - }, - commandType: CommandType.StoredProcedure); + try + { + var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds); + await connection.ExecuteAsync( + "[dbo].[Collection_CreateDefaultCollections]", + new + { + OrganizationId = organizationId, + DefaultCollectionName = defaultCollectionName, + OrganizationUserIdsJson = organizationUserIdsJson + }, + commandType: CommandType.StoredProcedure); + } + catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) + { + throw new DuplicateDefaultCollectionException(); + } } public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) @@ -417,6 +423,11 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer transaction.Commit(); } + catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) + { + transaction.Rollback(); + throw new DuplicateDefaultCollectionException(); + } catch { transaction.Rollback(); diff --git a/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs b/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs new file mode 100644 index 000000000000..4403fb4bfc2b --- /dev/null +++ b/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs @@ -0,0 +1,17 @@ +namespace Bit.Infrastructure.Dapper.Repositories; + +#nullable enable + +internal static class DatabaseExceptionHelpers +{ + /// + /// Determines if an exception represents a SQL Server duplicate key constraint violation. + /// + public static bool IsDuplicateKeyException(Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); + + return exception is Microsoft.Data.SqlClient.SqlException { Errors: not null } msEx && + msEx.Errors.Cast().Any(error => error.Number == 2627); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 09ec901aba09..fb0539b3c354 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -1,9 +1,10 @@ -using AutoMapper; +using System.Data.Common; +using AutoMapper; +using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; -using Bit.Core.Utilities; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories.Queries; @@ -810,20 +811,27 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - // CRITICAL: Insert semaphore entries BEFORE collections - // Database will throw on duplicate primary key (OrganizationUserId) - var now = DateTime.UtcNow; - var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore + try { - OrganizationUserId = c.OrganizationUserId, - CreationDate = now - }).ToList(); + // CRITICAL: Insert semaphore entries BEFORE collections + // Database will throw on duplicate primary key (OrganizationUserId) + var now = DateTime.UtcNow; + var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore + { + OrganizationUserId = c.OrganizationUserId, + CreationDate = now + }).ToList(); - await dbContext.BulkCopyAsync(semaphores); - await dbContext.BulkCopyAsync(Mapper.Map>(collections)); - await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); + await dbContext.BulkCopyAsync(semaphores); + await dbContext.BulkCopyAsync(Mapper.Map>(collections)); + await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); + } + catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) + { + throw new DuplicateDefaultCollectionException(); + } } public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs new file mode 100644 index 000000000000..e5c9ecbd9ae1 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs @@ -0,0 +1,37 @@ +using System.Data.Common; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Infrastructure.EntityFramework.Repositories; + +#nullable enable + +internal static class DatabaseExceptionHelpers +{ + /// + /// Determines if a DbUpdateException represents a duplicate key constraint violation. + /// Works with MySQL, SQL Server, PostgreSQL, and SQLite. + /// + public static bool IsDuplicateKeyException(Exception exception) + { + ArgumentNullException.ThrowIfNull(exception); + + switch (exception) + { + // MySQL + case MySqlConnector.MySqlException myEx: + return myEx.ErrorCode == MySqlConnector.MySqlErrorCode.DuplicateKeyEntry; + // SQL Server + case Microsoft.Data.SqlClient.SqlException msEx: + return msEx.Errors != null && + msEx.Errors.Cast().Any(error => error.Number == 2627); + // PostgreSQL + case Npgsql.PostgresException pgEx: + return pgEx.SqlState == "23505"; + // SQLite + case Microsoft.Data.Sqlite.SqliteException liteEx: + return liteEx is { SqliteErrorCode: 19, SqliteExtendedErrorCode: 1555 }; + default: + return false; + } + } +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs index 5851d8d468a0..b841681cf9df 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Entities; + +using Bit.Core.AdminConsole.Collections; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; @@ -96,8 +98,8 @@ public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenUsersAlr await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); - // Act - Try to create again, should throw database constraint exception - await Assert.ThrowsAnyAsync(() => + // Act - Try to create again, should throw specific duplicate collection exception + await Assert.ThrowsAsync(() => collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName)); // Assert - Original collections should remain unchanged @@ -125,7 +127,7 @@ public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenDuplicat await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, [existingUser.Id], defaultCollectionName); // Act - Try to create for both without filtering (incorrect usage) - await Assert.ThrowsAnyAsync(() => + await Assert.ThrowsAsync(() => collectionRepository.CreateDefaultCollectionsBulkAsync( organization.Id, [existingUser.Id, newUser.Id], diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index b8ca61a3c793..120951b30277 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using Bit.Core.AdminConsole.Collections; +using Bit.Core.Enums; using Bit.Core.Repositories; using Xunit; @@ -87,8 +88,8 @@ await collectionRepository.CreateDefaultCollectionsAsync( [orgUser.Id], "My Items"); - // Second call should throw and should not create duplicate - await Assert.ThrowsAnyAsync(() => + // Second call should throw specific exception and should not create duplicate + await Assert.ThrowsAsync(() => collectionRepository.CreateDefaultCollectionsAsync( organization.Id, [orgUser.Id], From 897719e69557261548e0b87981e07fabe3414afc Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 15:21:29 +1000 Subject: [PATCH 21/41] Move semaphore into helper method for consistent creation date --- .../Collections/CollectionUtils.cs | 21 ++++++++++++++----- .../Repositories/CollectionRepository.cs | 12 +++-------- .../Repositories/CollectionRepository.cs | 11 ++-------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs index e540c7555559..25eb81283776 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs @@ -7,16 +7,21 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Collections; public static class CollectionUtils { /// - /// Arranges Collection and CollectionUser objects to create default user collections. + /// Arranges semaphore, Collection and CollectionUser objects to create default user collections. /// /// The organization ID. /// The IDs for organization users who need default collections. /// The encrypted string to use as the default collection name. /// - public static (IEnumerable collection, IEnumerable collectionUsers) + public static (IEnumerable semaphores, + IEnumerable collection, + IEnumerable collectionUsers) BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { + var now = DateTime.UtcNow; + + var semaphores = new List(); var collectionUsers = new List(); var collections = new List(); @@ -24,13 +29,19 @@ public static (IEnumerable collection, IEnumerable c { var collectionId = CoreHelpers.GenerateComb(); + semaphores.Add(new DefaultCollectionSemaphore + { + OrganizationUserId = orgUserId, + CreationDate = now + }); + collections.Add(new Collection { Id = collectionId, OrganizationId = organizationId, Name = defaultCollectionName, - CreationDate = DateTime.UtcNow, - RevisionDate = DateTime.UtcNow, + CreationDate = now, + RevisionDate = now, Type = CollectionType.DefaultUserCollection, DefaultUserCollectionEmail = null @@ -46,6 +57,6 @@ public static (IEnumerable collection, IEnumerable c }); } - return (collections, collectionUsers); + return (semaphores, collections, collectionUsers); } } diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 3340fec8f1cc..001efe07ba54 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -398,7 +398,7 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer return; } - var (collections, collectionUsers) = + var (semaphores, collections, collectionUsers) = CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); await using var connection = new SqlConnection(ConnectionString); @@ -410,13 +410,6 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer // CRITICAL: Insert semaphore entries BEFORE collections // Database will throw on duplicate primary key (OrganizationUserId) - var now = DateTime.UtcNow; - var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore - { - OrganizationUserId = c.OrganizationUserId, - CreationDate = now - }).ToList(); - await BulkInsertDefaultCollectionSemaphoresAsync(connection, transaction, semaphores); await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); @@ -447,8 +440,9 @@ public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable return results.ToHashSet(); } - private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, List semaphores) + private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, IEnumerable semaphores) { + semaphores = semaphores.ToList(); if (!semaphores.Any()) { return; diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index fb0539b3c354..fa8ffb258e76 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -805,7 +805,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } - var (collections, collectionUsers) = + var (semaphores, collections, collectionUsers) = CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); using var scope = ServiceScopeFactory.CreateScope(); @@ -815,14 +815,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable { // CRITICAL: Insert semaphore entries BEFORE collections // Database will throw on duplicate primary key (OrganizationUserId) - var now = DateTime.UtcNow; - var semaphores = collectionUsers.Select(c => new DefaultCollectionSemaphore - { - OrganizationUserId = c.OrganizationUserId, - CreationDate = now - }).ToList(); - - await dbContext.BulkCopyAsync(semaphores); + await dbContext.BulkCopyAsync(Mapper.Map>(semaphores)); await dbContext.BulkCopyAsync(Mapper.Map>(collections)); await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); From 38bab4448f673f816a771b09bde5206b57ef3127 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 15:23:15 +1000 Subject: [PATCH 22/41] dotnet format --- .../Repositories/CollectionRepository.cs | 3 +-- .../Repositories/DatabaseExceptionHelpers.cs | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index fa8ffb258e76..e7806f5e775f 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -1,5 +1,4 @@ -using System.Data.Common; -using AutoMapper; +using AutoMapper; using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Enums; diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs index e5c9ecbd9ae1..7d297efcc9f6 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs @@ -1,7 +1,4 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore; - -namespace Bit.Infrastructure.EntityFramework.Repositories; +namespace Bit.Infrastructure.EntityFramework.Repositories; #nullable enable From 2bf848720bd2b5b462294c09357daf226b28474f Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 1 Jan 2026 15:31:47 +1000 Subject: [PATCH 23/41] local code review feedback --- .../Repositories/CollectionRepository.cs | 2 +- .../2025-12-30_02_PopulateDefaultCollectionSemaphore.sql | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 001efe07ba54..9a14612e142c 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -453,7 +453,7 @@ private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection conn .OrderBy(s => s.OrganizationUserId) .ToList(); - using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity & SqlBulkCopyOptions.CheckConstraints, transaction); + using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.CheckConstraints, transaction); bulkCopy.DestinationTableName = "[dbo].[DefaultCollectionSemaphore]"; bulkCopy.BatchSize = 500; bulkCopy.BulkCopyTimeout = 120; diff --git a/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql index 488377f0df4d..b8518b0eba8b 100644 --- a/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql +++ b/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql @@ -2,14 +2,12 @@ -- This migration is idempotent and can be run multiple times safely INSERT INTO [dbo].[DefaultCollectionSemaphore] ( - [OrganizationId], [OrganizationUserId], [CreationDate] ) SELECT DISTINCT - c.[OrganizationId], cu.[OrganizationUserId], - c.[CreationDate] + GETUTCDATE() FROM [dbo].[Collection] c INNER JOIN @@ -23,7 +21,6 @@ WHERE FROM [dbo].[DefaultCollectionSemaphore] dcs WHERE - dcs.[OrganizationId] = c.[OrganizationId] - AND dcs.[OrganizationUserId] = cu.[OrganizationUserId] + dcs.[OrganizationUserId] = cu.[OrganizationUserId] ); GO From 415182a9b8085aae137e08414859f75fa014d0a6 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Fri, 2 Jan 2026 08:15:01 +1000 Subject: [PATCH 24/41] Fix test --- .../ConfirmOrganizationUserCommandTests.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs index 071d7313fd4b..cf0e2c71ebd4 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs @@ -537,18 +537,9 @@ public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganiza sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true); - var policyDetails = new PolicyDetails - { - OrganizationId = org.Id, - OrganizationUserId = orgUser.Id, - IsProvider = false, - OrganizationUserStatus = orgUser.Status, - OrganizationUserType = orgUser.Type, - PolicyType = PolicyType.OrganizationDataOwnership - }; sutProvider.GetDependency() .GetAsync(orgUser.UserId!.Value) - .Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, [policyDetails])); + .Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, [])); await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName); From 8427367c764a1c8c5bc54433e1d932002e43fd2d Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Fri, 2 Jan 2026 08:36:12 +1000 Subject: [PATCH 25/41] Use guid array instead of json --- .../Repositories/CollectionRepository.cs | 3 +- .../Collection_CreateDefaultCollections.sql | 36 +++++++++---------- .../CreateDefaultCollectionsTests.cs | 2 +- ...01_Collection_CreateDefaultCollections.sql | 26 +++++++------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 9a14612e142c..f9b1a2c0f3e5 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -373,14 +373,13 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable try { - var organizationUserIdsJson = JsonSerializer.Serialize(organizationUserIds); await connection.ExecuteAsync( "[dbo].[Collection_CreateDefaultCollections]", new { OrganizationId = organizationId, DefaultCollectionName = defaultCollectionName, - OrganizationUserIdsJson = organizationUserIdsJson + OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure); } diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql index 98c6ee3b1890..f1c87e3b901b 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql @@ -1,32 +1,32 @@ -- Creates default user collections for organization users -- Uses semaphore table to prevent duplicate default collections at database level --- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) --- Organization FK uses NoAction to avoid competing cascade paths CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), - @OrganizationUserIdsJson NVARCHAR(MAX) + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY AS BEGIN SET NOCOUNT ON - -- Parse JSON once into table variable with pre-generated collection IDs - DECLARE @OrganizationUserIds TABLE - ( - [OrganizationUserId] UNIQUEIDENTIFIER, - [CollectionId] UNIQUEIDENTIFIER - ); + DECLARE @Now DATETIME2(7) = GETUTCDATE() + + -- Create temporary table to allocate collection IDs to each organizationUser + DECLARE @CollectionsToInsert TABLE + ( + [OrganizationUserId] UNIQUEIDENTIFIER, + [CollectionId] UNIQUEIDENTIFIER + ); - INSERT INTO @OrganizationUserIds + INSERT INTO @CollectionsToInsert ( [OrganizationUserId], [CollectionId] ) SELECT - CAST([value] AS UNIQUEIDENTIFIER), + ou.Id, NEWID() FROM - OPENJSON(@OrganizationUserIdsJson); + @OrganizationUserIds ou BEGIN TRANSACTION; @@ -40,9 +40,9 @@ BEGIN ) SELECT ou.[OrganizationUserId], - GETUTCDATE() + @Now FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; -- Insert collections for users who obtained semaphore entries INSERT INTO [dbo].[Collection] @@ -60,13 +60,13 @@ BEGIN ou.[CollectionId], @OrganizationId, @DefaultCollectionName, - GETUTCDATE(), - GETUTCDATE(), + @Now, + @Now, 1, -- CollectionType.DefaultUserCollection NULL, NULL FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -84,7 +84,7 @@ BEGIN 0, -- HidePasswords = false 1 -- Manage = true FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; COMMIT TRANSACTION; END TRY diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 120951b30277..224a9bdc29ab 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -71,7 +71,7 @@ await collectionRepository.CreateDefaultCollectionsAsync( /// Test that calling CreateDefaultCollectionsAsync multiple times does NOT create duplicates /// [Theory, DatabaseData] - public async Task CreateDefaultCollectionsAsync_CalledMultipleTimes_DoesNotCreateDuplicates( + public async Task CreateDefaultCollectionsAsync_CalledMultipleTimesForSameOrganizationUser_Throws( IUserRepository userRepository, IOrganizationRepository organizationRepository, ICollectionRepository collectionRepository, diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql index 3e8fe14b1a6b..401e58005cbb 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql @@ -1,28 +1,30 @@ CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), - @OrganizationUserIdsJson NVARCHAR(MAX) + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY AS BEGIN SET NOCOUNT ON - -- Parse JSON once into table variable with pre-generated collection IDs - DECLARE @OrganizationUserIds TABLE + DECLARE @Now DATETIME2(7) = GETUTCDATE() + + -- Create temporary table to allocate collection IDs to each organizationUser + DECLARE @CollectionsToInsert TABLE ( [OrganizationUserId] UNIQUEIDENTIFIER, [CollectionId] UNIQUEIDENTIFIER ); - INSERT INTO @OrganizationUserIds + INSERT INTO @CollectionsToInsert ( [OrganizationUserId], [CollectionId] ) SELECT - CAST([value] AS UNIQUEIDENTIFIER), + ou.Id, NEWID() FROM - OPENJSON(@OrganizationUserIdsJson); + @OrganizationUserIds ou BEGIN TRANSACTION; @@ -36,9 +38,9 @@ BEGIN ) SELECT ou.[OrganizationUserId], - GETUTCDATE() + @Now FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; -- Insert collections for users who obtained semaphore entries INSERT INTO [dbo].[Collection] @@ -56,13 +58,13 @@ BEGIN ou.[CollectionId], @OrganizationId, @DefaultCollectionName, - GETUTCDATE(), - GETUTCDATE(), + @Now, + @Now, 1, -- CollectionType.DefaultUserCollection NULL, NULL FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -80,7 +82,7 @@ BEGIN 0, -- HidePasswords = false 1 -- Manage = true FROM - @OrganizationUserIds ou; + @CollectionsToInsert ou; COMMIT TRANSACTION; END TRY From 51800923ef0dc784be1539ea5e26cfdeac09653e Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Fri, 2 Jan 2026 13:27:59 +1000 Subject: [PATCH 26/41] Trigger code review From 0face3a06e462879b13fd7420543903f21a863da Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Fri, 2 Jan 2026 13:39:34 +1000 Subject: [PATCH 27/41] Add explicit transaction in EF --- .../Repositories/CollectionRepository.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index e7806f5e775f..786e5cbd8b17 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -809,6 +809,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); + await using var transaction = await dbContext.Database.BeginTransactionAsync(); try { @@ -819,9 +820,11 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); await dbContext.SaveChangesAsync(); + await transaction.CommitAsync(); } catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) { + await transaction.RollbackAsync(); throw new DuplicateDefaultCollectionException(); } } From 1a42f6f630db61a6c2a26958b3b7e0135997179b Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Fri, 2 Jan 2026 13:40:18 +1000 Subject: [PATCH 28/41] Add general catch block --- .../Repositories/CollectionRepository.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 786e5cbd8b17..283e2b7efcad 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -827,6 +827,10 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await transaction.RollbackAsync(); throw new DuplicateDefaultCollectionException(); } + catch + { + await transaction.RollbackAsync(); + } } public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) From c84dd78463f1f057e256bb0e1c0fb4eab2c8200f Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:46:02 +1000 Subject: [PATCH 29/41] Rethrow exception Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- .../Repositories/CollectionRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 283e2b7efcad..736c1e97757e 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -830,6 +830,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable catch { await transaction.RollbackAsync(); + throw; } } From aa1c0a4a777a4d685d1a9ff4f17117757a34dd80 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Sat, 3 Jan 2026 13:20:43 +1000 Subject: [PATCH 30/41] Rui PR feedback --- .../OrganizationFeatures/Collections/CollectionUtils.cs | 4 ++-- .../Repositories/CollectionRepository.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs index 25eb81283776..fddfd37d8e8b 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs @@ -12,9 +12,9 @@ public static class CollectionUtils /// The organization ID. /// The IDs for organization users who need default collections. /// The encrypted string to use as the default collection name. - /// + /// A tuple containing the semaphores, collections, and collection users. public static (IEnumerable semaphores, - IEnumerable collection, + IEnumerable collections, IEnumerable collectionUsers) BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index f9b1a2c0f3e5..f7fecb91aad4 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -406,7 +406,6 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer try { - // CRITICAL: Insert semaphore entries BEFORE collections // Database will throw on duplicate primary key (OrganizationUserId) await BulkInsertDefaultCollectionSemaphoresAsync(connection, transaction, semaphores); From 2b7caf1e3780a71ed5264821a38b32f23c774fdd Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Sat, 3 Jan 2026 13:41:44 +1000 Subject: [PATCH 31/41] Use TwoGuidIdArray --- src/Infrastructure.Dapper/DapperHelpers.cs | 15 ++++++++ .../Repositories/CollectionRepository.cs | 7 +++- .../Collection_CreateDefaultCollections.sql | 34 +++++------------- ...01_Collection_CreateDefaultCollections.sql | 36 ++++++------------- 4 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/Infrastructure.Dapper/DapperHelpers.cs b/src/Infrastructure.Dapper/DapperHelpers.cs index 9a119e1e320d..4384a6f7527b 100644 --- a/src/Infrastructure.Dapper/DapperHelpers.cs +++ b/src/Infrastructure.Dapper/DapperHelpers.cs @@ -160,6 +160,21 @@ public static DataTable ToGuidIdArrayTVP(this IEnumerable ids) return ids.ToArrayTVP("GuidId"); } + public static DataTable ToTwoGuidIdArrayTVP(this IEnumerable<(Guid id1, Guid id2)> values) + { + var table = new DataTable(); + table.SetTypeName("[dbo].[TwoGuidIdArray]"); + table.Columns.Add("Id1", typeof(Guid)); + table.Columns.Add("Id2", typeof(Guid)); + + foreach (var value in values) + { + table.Rows.Add(value.id1, value.id2); + } + + return table; + } + public static DataTable ToArrayTVP(this IEnumerable values, string columnName) { var table = new DataTable(); diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index f7fecb91aad4..36f4f914a35b 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -7,6 +7,7 @@ using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; +using Bit.Core.Utilities; using Bit.Infrastructure.Dapper.AdminConsole.Helpers; using Dapper; using Microsoft.Data.SqlClient; @@ -368,6 +369,10 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } + var organizationUserCollectionIds = organizationUserIds + .Select(ou => (ou, CoreHelpers.GenerateComb())) + .ToTwoGuidIdArrayTVP(); + await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); @@ -379,7 +384,7 @@ await connection.ExecuteAsync( { OrganizationId = organizationId, DefaultCollectionName = defaultCollectionName, - OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() + OrganizationUserCollectionIds = organizationUserCollectionIds }, commandType: CommandType.StoredProcedure); } diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql index f1c87e3b901b..b7424dd34816 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql @@ -3,31 +3,13 @@ CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), - @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY + @OrganizationUserCollectionIds AS [dbo].[TwoGuidIdArray] READONLY -- OrganizationUserId, CollectionId AS BEGIN SET NOCOUNT ON DECLARE @Now DATETIME2(7) = GETUTCDATE() - -- Create temporary table to allocate collection IDs to each organizationUser - DECLARE @CollectionsToInsert TABLE - ( - [OrganizationUserId] UNIQUEIDENTIFIER, - [CollectionId] UNIQUEIDENTIFIER - ); - - INSERT INTO @CollectionsToInsert - ( - [OrganizationUserId], - [CollectionId] - ) - SELECT - ou.Id, - NEWID() - FROM - @OrganizationUserIds ou - BEGIN TRANSACTION; BEGIN TRY @@ -39,10 +21,10 @@ BEGIN [CreationDate] ) SELECT - ou.[OrganizationUserId], + ids.[Id1], -- OrganizationUserId @Now FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; -- Insert collections for users who obtained semaphore entries INSERT INTO [dbo].[Collection] @@ -57,7 +39,7 @@ BEGIN [DefaultUserCollectionEmail] ) SELECT - ou.[CollectionId], + ids.[Id2], -- CollectionId @OrganizationId, @DefaultCollectionName, @Now, @@ -66,7 +48,7 @@ BEGIN NULL, NULL FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -78,13 +60,13 @@ BEGIN [Manage] ) SELECT - ou.[CollectionId], - ou.[OrganizationUserId], + ids.[Id2], -- CollectionId + ids.[Id1], -- OrganizationUserId 0, -- ReadOnly = false 0, -- HidePasswords = false 1 -- Manage = true FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; COMMIT TRANSACTION; END TRY diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql index 401e58005cbb..0d08dcf4f20b 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql @@ -1,31 +1,15 @@ +-- Creates default user collections for organization users +-- Uses semaphore table to prevent duplicate default collections at database level CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), - @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY + @OrganizationUserCollectionIds AS [dbo].[TwoGuidIdArray] READONLY -- OrganizationUserId, CollectionId AS BEGIN SET NOCOUNT ON DECLARE @Now DATETIME2(7) = GETUTCDATE() - -- Create temporary table to allocate collection IDs to each organizationUser - DECLARE @CollectionsToInsert TABLE - ( - [OrganizationUserId] UNIQUEIDENTIFIER, - [CollectionId] UNIQUEIDENTIFIER - ); - - INSERT INTO @CollectionsToInsert - ( - [OrganizationUserId], - [CollectionId] - ) - SELECT - ou.Id, - NEWID() - FROM - @OrganizationUserIds ou - BEGIN TRANSACTION; BEGIN TRY @@ -37,10 +21,10 @@ BEGIN [CreationDate] ) SELECT - ou.[OrganizationUserId], + ids.[Id1], -- OrganizationUserId @Now FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; -- Insert collections for users who obtained semaphore entries INSERT INTO [dbo].[Collection] @@ -55,7 +39,7 @@ BEGIN [DefaultUserCollectionEmail] ) SELECT - ou.[CollectionId], + ids.[Id2], -- CollectionId @OrganizationId, @DefaultCollectionName, @Now, @@ -64,7 +48,7 @@ BEGIN NULL, NULL FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -76,13 +60,13 @@ BEGIN [Manage] ) SELECT - ou.[CollectionId], - ou.[OrganizationUserId], + ids.[Id2], -- CollectionId + ids.[Id1], -- OrganizationUserId 0, -- ReadOnly = false 0, -- HidePasswords = false 1 -- Manage = true FROM - @CollectionsToInsert ou; + @OrganizationUserCollectionIds ids; COMMIT TRANSACTION; END TRY From 2fc0257d1dda19a0ff1c28c636a3ee1e9e562fc6 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Sat, 3 Jan 2026 13:49:38 +1000 Subject: [PATCH 32/41] Move transaction up to code layer --- .../Repositories/CollectionRepository.cs | 17 ++- .../Repositories/CollectionRepository.cs | 1 - .../Collection_CreateDefaultCollections.sql | 117 ++++++++---------- ...01_Collection_CreateDefaultCollections.sql | 117 ++++++++---------- 4 files changed, 119 insertions(+), 133 deletions(-) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 36f4f914a35b..469747fdbb35 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -375,6 +375,7 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await using var connection = new SqlConnection(ConnectionString); await connection.OpenAsync(); + await using var transaction = connection.BeginTransaction(); try { @@ -386,12 +387,20 @@ await connection.ExecuteAsync( DefaultCollectionName = defaultCollectionName, OrganizationUserCollectionIds = organizationUserCollectionIds }, - commandType: CommandType.StoredProcedure); + commandType: CommandType.StoredProcedure, + transaction: transaction); + + await transaction.CommitAsync(); } catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) { + await transaction.RollbackAsync(); throw new DuplicateDefaultCollectionException(); } + catch + { + await transaction.RollbackAsync(); + } } public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) @@ -417,16 +426,16 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); - transaction.Commit(); + await transaction.CommitAsync(); } catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) { - transaction.Rollback(); + await transaction.RollbackAsync(); throw new DuplicateDefaultCollectionException(); } catch { - transaction.Rollback(); + await transaction.RollbackAsync(); throw; } } diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 736c1e97757e..65bdb68312c3 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -819,7 +819,6 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable await dbContext.BulkCopyAsync(Mapper.Map>(collections)); await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); - await dbContext.SaveChangesAsync(); await transaction.CommitAsync(); } catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql index b7424dd34816..f751b8776e08 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql @@ -1,5 +1,6 @@ -- Creates default user collections for organization users -- Uses semaphore table to prevent duplicate default collections at database level +-- NOTE: this MUST be executed in a single transaction to obtain semaphore protection CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @@ -10,70 +11,58 @@ BEGIN DECLARE @Now DATETIME2(7) = GETUTCDATE() - BEGIN TRANSACTION; + -- Insert semaphore entries first to obtain the "lock" + -- If this fails due to duplicate key, the entire transaction will be rolled back + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationUserId], + [CreationDate] + ) + SELECT + ids.[Id1], -- OrganizationUserId + @Now + FROM + @OrganizationUserCollectionIds ids; - BEGIN TRY - -- Insert semaphore entries first to obtain the "lock" - -- If this fails due to duplicate key, the entire transaction will be rolled back - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationUserId], - [CreationDate] - ) - SELECT - ids.[Id1], -- OrganizationUserId - @Now - FROM - @OrganizationUserCollectionIds ids; + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ids.[Id2], -- CollectionId + @OrganizationId, + @DefaultCollectionName, + @Now, + @Now, + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserCollectionIds ids; - -- Insert collections for users who obtained semaphore entries - INSERT INTO [dbo].[Collection] - ( - [Id], - [OrganizationId], - [Name], - [CreationDate], - [RevisionDate], - [Type], - [ExternalId], - [DefaultUserCollectionEmail] - ) - SELECT - ids.[Id2], -- CollectionId - @OrganizationId, - @DefaultCollectionName, - @Now, - @Now, - 1, -- CollectionType.DefaultUserCollection - NULL, - NULL - FROM - @OrganizationUserCollectionIds ids; - - -- Insert collection user mappings - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - ids.[Id2], -- CollectionId - ids.[Id1], -- OrganizationUserId - 0, -- ReadOnly = false - 0, -- HidePasswords = false - 1 -- Manage = true - FROM - @OrganizationUserCollectionIds ids; - - COMMIT TRANSACTION; - END TRY - BEGIN CATCH - IF @@TRANCOUNT > 0 - ROLLBACK TRANSACTION; - - THROW; - END CATCH + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ids.[Id2], -- CollectionId + ids.[Id1], -- OrganizationUserId + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserCollectionIds ids; END diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql index 0d08dcf4f20b..a03040c5716d 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql @@ -1,5 +1,6 @@ -- Creates default user collections for organization users -- Uses semaphore table to prevent duplicate default collections at database level +-- NOTE: this MUST be executed in a single transaction to obtain semaphore protection CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @@ -10,71 +11,59 @@ BEGIN DECLARE @Now DATETIME2(7) = GETUTCDATE() - BEGIN TRANSACTION; + -- Insert semaphore entries first to obtain the "lock" + -- If this fails due to duplicate key, the entire transaction will be rolled back + INSERT INTO [dbo].[DefaultCollectionSemaphore] + ( + [OrganizationUserId], + [CreationDate] + ) + SELECT + ids.[Id1], -- OrganizationUserId + @Now + FROM + @OrganizationUserCollectionIds ids; - BEGIN TRY - -- Insert semaphore entries first to obtain the "lock" - -- If this fails due to duplicate key, the entire transaction will be rolled back - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationUserId], - [CreationDate] - ) - SELECT - ids.[Id1], -- OrganizationUserId - @Now - FROM - @OrganizationUserCollectionIds ids; + -- Insert collections for users who obtained semaphore entries + INSERT INTO [dbo].[Collection] + ( + [Id], + [OrganizationId], + [Name], + [CreationDate], + [RevisionDate], + [Type], + [ExternalId], + [DefaultUserCollectionEmail] + ) + SELECT + ids.[Id2], -- CollectionId + @OrganizationId, + @DefaultCollectionName, + @Now, + @Now, + 1, -- CollectionType.DefaultUserCollection + NULL, + NULL + FROM + @OrganizationUserCollectionIds ids; - -- Insert collections for users who obtained semaphore entries - INSERT INTO [dbo].[Collection] - ( - [Id], - [OrganizationId], - [Name], - [CreationDate], - [RevisionDate], - [Type], - [ExternalId], - [DefaultUserCollectionEmail] - ) - SELECT - ids.[Id2], -- CollectionId - @OrganizationId, - @DefaultCollectionName, - @Now, - @Now, - 1, -- CollectionType.DefaultUserCollection - NULL, - NULL - FROM - @OrganizationUserCollectionIds ids; - - -- Insert collection user mappings - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - ids.[Id2], -- CollectionId - ids.[Id1], -- OrganizationUserId - 0, -- ReadOnly = false - 0, -- HidePasswords = false - 1 -- Manage = true - FROM - @OrganizationUserCollectionIds ids; - - COMMIT TRANSACTION; - END TRY - BEGIN CATCH - IF @@TRANCOUNT > 0 - ROLLBACK TRANSACTION; - - THROW; - END CATCH + -- Insert collection user mappings + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + ids.[Id2], -- CollectionId + ids.[Id1], -- OrganizationUserId + 0, -- ReadOnly = false + 0, -- HidePasswords = false + 1 -- Manage = true + FROM + @OrganizationUserCollectionIds ids; END GO From fc351ceb60427761331edb6ad7d47692683713b1 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Sat, 3 Jan 2026 13:56:51 +1000 Subject: [PATCH 33/41] Rethrow exception Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- src/Infrastructure.Dapper/Repositories/CollectionRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 469747fdbb35..1416945936cf 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -400,6 +400,7 @@ await connection.ExecuteAsync( catch { await transaction.RollbackAsync(); + throw; } } From 8f345e068937d6059766497b71393cce16856e2d Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 13:52:34 +1000 Subject: [PATCH 34/41] First pass at reverting semaphore approach --- .../DuplicateDefaultCollectionException.cs | 5 - .../Collections/CollectionUtils.cs | 16 +- ...rganizationDataOwnershipPolicyValidator.cs | 11 +- .../Entities/DefaultCollectionSemaphore.cs | 7 - .../Repositories/ICollectionRepository.cs | 20 +- .../Repositories/CollectionRepository.cs | 99 +- ...lectionSemaphoreEntityTypeConfiguration.cs | 23 - .../Models/DefaultCollectionSemaphore.cs | 19 - .../Repositories/CollectionRepository.cs | 47 +- .../Repositories/DatabaseContext.cs | 1 - .../Collection_CreateDefaultCollections.sql | 42 +- .../Tables/DefaultCollectionSemaphore.sql | 11 - ...ionSemaphore_ReadByOrganizationUserIds.sql | 13 - ...zationDataOwnershipPolicyValidatorTests.cs | 57 +- .../CreateDefaultCollectionsBulkTests.cs | 57 +- .../CreateDefaultCollectionsTests.cs | 22 +- .../DefaultCollectionSemaphoreTests.cs | 72 - ...25-12-30_00_DefaultCollectionSemaphore.sql | 31 - ...01_Collection_CreateDefaultCollections.sql | 42 +- ..._02_PopulateDefaultCollectionSemaphore.sql | 26 - ...256_DefaultCollectionSemaphore.Designer.cs | 3467 ---------------- ...251231233256_DefaultCollectionSemaphore.cs | 39 - .../DatabaseContextModelSnapshot.cs | 24 - ...251_DefaultCollectionSemaphore.Designer.cs | 3473 ----------------- ...251231233251_DefaultCollectionSemaphore.cs | 38 - .../DatabaseContextModelSnapshot.cs | 24 - ...301_DefaultCollectionSemaphore.Designer.cs | 3456 ---------------- ...251231233301_DefaultCollectionSemaphore.cs | 38 - .../DatabaseContextModelSnapshot.cs | 24 - 29 files changed, 113 insertions(+), 11091 deletions(-) delete mode 100644 src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs delete mode 100644 src/Core/Entities/DefaultCollectionSemaphore.cs delete mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs delete mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs delete mode 100644 src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql delete mode 100644 src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql delete mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs delete mode 100644 util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql delete mode 100644 util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql delete mode 100644 util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs delete mode 100644 util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs delete mode 100644 util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs delete mode 100644 util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs delete mode 100644 util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs delete mode 100644 util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs diff --git a/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs b/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs deleted file mode 100644 index abe1cdedb085..000000000000 --- a/src/Core/AdminConsole/Collections/DuplicateDefaultCollectionException.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Bit.Core.AdminConsole.Collections; - -public class DuplicateDefaultCollectionException() - : Exception("A My Items collection already exists for one or more of the specified organization members."); - diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs index fddfd37d8e8b..6b2da70d3ea9 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs @@ -7,21 +7,19 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Collections; public static class CollectionUtils { /// - /// Arranges semaphore, Collection and CollectionUser objects to create default user collections. + /// Arranges Collection and CollectionUser objects to create default user collections. /// /// The organization ID. /// The IDs for organization users who need default collections. /// The encrypted string to use as the default collection name. - /// A tuple containing the semaphores, collections, and collection users. - public static (IEnumerable semaphores, - IEnumerable collections, + /// A tuple containing the collections and collection users. + public static (IEnumerable collections, IEnumerable collectionUsers) BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { var now = DateTime.UtcNow; - var semaphores = new List(); var collectionUsers = new List(); var collections = new List(); @@ -29,12 +27,6 @@ public static (IEnumerable semaphores, { var collectionId = CoreHelpers.GenerateComb(); - semaphores.Add(new DefaultCollectionSemaphore - { - OrganizationUserId = orgUserId, - CreationDate = now - }); - collections.Add(new Collection { Id = collectionId, @@ -57,6 +49,6 @@ public static (IEnumerable semaphores, }); } - return (semaphores, collections, collectionUsers); + return (collections, collectionUsers); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs index 22d9fd20dd06..6f763b18be16 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs @@ -72,18 +72,9 @@ private async Task UpsertDefaultCollectionsForUsersAsync(PolicyUpdate policyUpda return; } - // Filter out users who already have default collections - var existingSemaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(userOrgIds); - var usersNeedingDefaultCollections = userOrgIds.Except(existingSemaphores).ToList(); - - if (!usersNeedingDefaultCollections.Any()) - { - return; - } - await collectionRepository.CreateDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, - usersNeedingDefaultCollections, + userOrgIds, defaultCollectionName); } } diff --git a/src/Core/Entities/DefaultCollectionSemaphore.cs b/src/Core/Entities/DefaultCollectionSemaphore.cs deleted file mode 100644 index ea2568e671eb..000000000000 --- a/src/Core/Entities/DefaultCollectionSemaphore.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bit.Core.Entities; - -public class DefaultCollectionSemaphore -{ - public Guid OrganizationUserId { get; set; } - public DateTime CreationDate { get; set; } = DateTime.UtcNow; -} diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index 91232db05859..3f3b71d2d5e8 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -65,7 +65,7 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col /// /// Creates default user collections for the specified organization users. - /// Throws an exception if any user already has a default collection for the organization. + /// Filters internally to only create collections for users who don't already have one. /// /// The Organization ID. /// The Organization User IDs to create default collections for. @@ -75,27 +75,11 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col /// /// Creates default user collections for the specified organization users using bulk insert operations. /// Use this if you need to create collections for > ~1k users. - /// Throws an exception if any user already has a default collection for the organization. + /// Filters internally to only create collections for users who don't already have one. /// /// The Organization ID. /// The Organization User IDs to create default collections for. /// The encrypted string to use as the default collection name. - /// - /// If any of the OrganizationUsers may already have default collections, the caller should first filter out these - /// users using GetDefaultCollectionSemaphoresAsync before calling this method. - /// Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName); - /// - /// Gets default collection semaphores for the given organizationUserIds. - /// If an organizationUserId is missing from the result set, they do not have a semaphore set. - /// - /// The organization User IDs to check semaphores for. - /// Collection of organization user IDs that have default collection semaphores. - /// - /// The semaphore table is used to ensure that an organizationUser can only have 1 default collection. - /// (That is, a user may only have 1 default collection per organization.) - /// If a semaphore is returned, that user already has a default collection for that organization. - /// - Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds); } diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 1416945936cf..a78a699b10ac 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -1,7 +1,6 @@ using System.Data; using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Entities; using Bit.Core.Models.Data; @@ -392,11 +391,6 @@ await connection.ExecuteAsync( await transaction.CommitAsync(); } - catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) - { - await transaction.RollbackAsync(); - throw new DuplicateDefaultCollectionException(); - } catch { await transaction.RollbackAsync(); @@ -406,97 +400,8 @@ await connection.ExecuteAsync( public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { - organizationUserIds = organizationUserIds.ToList(); - if (!organizationUserIds.Any()) - { - return; - } - - var (semaphores, collections, collectionUsers) = - CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); - - await using var connection = new SqlConnection(ConnectionString); - connection.Open(); - await using var transaction = connection.BeginTransaction(); - - try - { - // CRITICAL: Insert semaphore entries BEFORE collections - // Database will throw on duplicate primary key (OrganizationUserId) - await BulkInsertDefaultCollectionSemaphoresAsync(connection, transaction, semaphores); - await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); - await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); - - await transaction.CommitAsync(); - } - catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) - { - await transaction.RollbackAsync(); - throw new DuplicateDefaultCollectionException(); - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } - - public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds) - { - await using var connection = new SqlConnection(ConnectionString); - - var results = await connection.QueryAsync( - "[dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds]", - new { OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() }, - commandType: CommandType.StoredProcedure); - - return results.ToHashSet(); - } - - private async Task BulkInsertDefaultCollectionSemaphoresAsync(SqlConnection connection, SqlTransaction transaction, IEnumerable semaphores) - { - semaphores = semaphores.ToList(); - if (!semaphores.Any()) - { - return; - } - - // Sort by composite key to reduce deadlocks - var sortedSemaphores = semaphores - .OrderBy(s => s.OrganizationUserId) - .ToList(); - - using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.CheckConstraints, transaction); - bulkCopy.DestinationTableName = "[dbo].[DefaultCollectionSemaphore]"; - bulkCopy.BatchSize = 500; - bulkCopy.BulkCopyTimeout = 120; - bulkCopy.EnableStreaming = true; - - var dataTable = new DataTable("DefaultCollectionSemaphoreDataTable"); - - var organizationUserIdColumn = new DataColumn(nameof(DefaultCollectionSemaphore.OrganizationUserId), typeof(Guid)); - dataTable.Columns.Add(organizationUserIdColumn); - var creationDateColumn = new DataColumn(nameof(DefaultCollectionSemaphore.CreationDate), typeof(DateTime)); - dataTable.Columns.Add(creationDateColumn); - - foreach (DataColumn col in dataTable.Columns) - { - bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName); - } - - var keys = new DataColumn[1]; - keys[0] = organizationUserIdColumn; - dataTable.PrimaryKey = keys; - - foreach (var semaphore in sortedSemaphores) - { - var row = dataTable.NewRow(); - row[organizationUserIdColumn] = semaphore.OrganizationUserId; - row[creationDateColumn] = semaphore.CreationDate; - dataTable.Rows.Add(row); - } - - await bulkCopy.WriteToServerAsync(dataTable); + // Use the stored procedure approach which handles filtering internally + await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } public class CollectionWithGroupsAndUsers : Collection diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs deleted file mode 100644 index 389294111157..000000000000 --- a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/DefaultCollectionSemaphoreEntityTypeConfiguration.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Configurations; - -public class DefaultCollectionSemaphoreEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder - .HasKey(dcs => new { dcs.OrganizationUserId }); - - // OrganizationUser FK cascades deletions to ensure automatic cleanup - builder - .HasOne(dcs => dcs.OrganizationUser) - .WithMany() - .HasForeignKey(dcs => dcs.OrganizationUserId) - .OnDelete(DeleteBehavior.Cascade); - - builder.ToTable(nameof(DefaultCollectionSemaphore)); - } -} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs deleted file mode 100644 index 6d51bb9f219c..000000000000 --- a/src/Infrastructure.EntityFramework/AdminConsole/Models/DefaultCollectionSemaphore.cs +++ /dev/null @@ -1,19 +0,0 @@ -using AutoMapper; -using Bit.Infrastructure.EntityFramework.Models; - -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; - -public class DefaultCollectionSemaphore : Core.Entities.DefaultCollectionSemaphore -{ - public virtual OrganizationUser? OrganizationUser { get; set; } -} - -public class DefaultCollectionSemaphoreMapperProfile : Profile -{ - public DefaultCollectionSemaphoreMapperProfile() - { - CreateMap() - .ForMember(dcs => dcs.OrganizationUser, opt => opt.Ignore()) - .ReverseMap(); - } -} diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 65bdb68312c3..6211f7187308 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -1,5 +1,4 @@ using AutoMapper; -using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -804,28 +803,38 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable return; } - var (semaphores, collections, collectionUsers) = - CollectionUtils.BuildDefaultUserCollections(organizationId, organizationUserIds, defaultCollectionName); - using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); + + // Query for users who already have default collections + var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); + var existingOrgUserIds = await dbContext.CollectionUsers + .Where(cu => organizationUserIdsHashSet.Contains(cu.OrganizationUserId)) + .Where(cu => cu.Collection.Type == CollectionType.DefaultUserCollection) + .Where(cu => cu.Collection.OrganizationId == organizationId) + .Select(cu => cu.OrganizationUserId) + .ToListAsync(); + + // Filter to only users who need collections + var filteredOrgUserIds = organizationUserIds.Except(existingOrgUserIds).ToList(); + + if (!filteredOrgUserIds.Any()) + { + return; + } + + var (collections, collectionUsers) = + CollectionUtils.BuildDefaultUserCollections(organizationId, filteredOrgUserIds, defaultCollectionName); + await using var transaction = await dbContext.Database.BeginTransactionAsync(); try { - // CRITICAL: Insert semaphore entries BEFORE collections - // Database will throw on duplicate primary key (OrganizationUserId) - await dbContext.BulkCopyAsync(Mapper.Map>(semaphores)); await dbContext.BulkCopyAsync(Mapper.Map>(collections)); await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); await transaction.CommitAsync(); } - catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex)) - { - await transaction.RollbackAsync(); - throw new DuplicateDefaultCollectionException(); - } catch { await transaction.RollbackAsync(); @@ -839,18 +848,4 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } - public async Task> GetDefaultCollectionSemaphoresAsync(IEnumerable organizationUserIds) - { - var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); - - using var scope = ServiceScopeFactory.CreateScope(); - var dbContext = GetDatabaseContext(scope); - - var result = await dbContext.DefaultCollectionSemaphores - .Where(s => organizationUserIdsHashSet.Contains(s.OrganizationUserId)) - .Select(s => s.OrganizationUserId) - .ToListAsync(); - - return result.ToHashSet(); - } } diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index f48bbc6c24f9..3ddcad55c3d1 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -43,7 +43,6 @@ public DatabaseContext(DbContextOptions options) public DbSet CollectionCiphers { get; set; } public DbSet CollectionGroups { get; set; } public DbSet CollectionUsers { get; set; } - public DbSet DefaultCollectionSemaphores { get; set; } public DbSet Devices { get; set; } public DbSet EmergencyAccesses { get; set; } public DbSet Events { get; set; } diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql index f751b8776e08..b110a7566d11 100644 --- a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql +++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql @@ -1,6 +1,6 @@ -- Creates default user collections for organization users --- Uses semaphore table to prevent duplicate default collections at database level --- NOTE: this MUST be executed in a single transaction to obtain semaphore protection +-- Filters out existing default collections at database level +-- NOTE: this MUST be executed in a single transaction to ensure consistency CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @@ -11,20 +11,20 @@ BEGIN DECLARE @Now DATETIME2(7) = GETUTCDATE() - -- Insert semaphore entries first to obtain the "lock" - -- If this fails due to duplicate key, the entire transaction will be rolled back - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationUserId], - [CreationDate] - ) - SELECT - ids.[Id1], -- OrganizationUserId - @Now - FROM - @OrganizationUserCollectionIds ids; + -- Filter to only users who don't have default collections + SELECT ids.Id1, ids.Id2 + INTO #FilteredIds + FROM @OrganizationUserCollectionIds ids + WHERE NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionUser] cu + INNER JOIN [dbo].[Collection] c ON c.Id = cu.CollectionId + WHERE c.OrganizationId = @OrganizationId + AND c.[Type] = 1 -- CollectionType.DefaultUserCollection + AND cu.OrganizationUserId = ids.Id1 + ); - -- Insert collections for users who obtained semaphore entries + -- Insert collections only for users who don't have default collections yet INSERT INTO [dbo].[Collection] ( [Id], @@ -37,7 +37,7 @@ BEGIN [DefaultUserCollectionEmail] ) SELECT - ids.[Id2], -- CollectionId + ids.Id2, -- CollectionId @OrganizationId, @DefaultCollectionName, @Now, @@ -46,7 +46,7 @@ BEGIN NULL, NULL FROM - @OrganizationUserCollectionIds ids; + #FilteredIds ids; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -58,11 +58,13 @@ BEGIN [Manage] ) SELECT - ids.[Id2], -- CollectionId - ids.[Id1], -- OrganizationUserId + ids.Id2, -- CollectionId + ids.Id1, -- OrganizationUserId 0, -- ReadOnly = false 0, -- HidePasswords = false 1 -- Manage = true FROM - @OrganizationUserCollectionIds ids; + #FilteredIds ids; + + DROP TABLE #FilteredIds; END diff --git a/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql b/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql deleted file mode 100644 index 6161781c8cc4..000000000000 --- a/src/Sql/dbo/AdminConsole/Tables/DefaultCollectionSemaphore.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Semaphore table to prevent duplicate default collections per organization user --- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) --- OrganizationId FK has NO ACTION to avoid competing cascade paths -CREATE TABLE [dbo].[DefaultCollectionSemaphore] -( - [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, - [CreationDate] DATETIME2 (7) NOT NULL, - CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationUserId] ASC), - CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) - REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE -); diff --git a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql b/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql deleted file mode 100644 index b1bfae98b689..000000000000 --- a/src/Sql/dbo/Stored Procedures/DefaultCollectionSemaphore_ReadByOrganizationUserIds.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds] - @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY -AS -BEGIN - SET NOCOUNT ON - - SELECT - [OrganizationUserId] - FROM - [dbo].[DefaultCollectionSemaphore] DCS - INNER JOIN - @OrganizationUserIds OU ON [OU].[Id] = [DCS].[OrganizationUserId] -END diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs index b61888759ab5..5d6bac0ead6a 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs @@ -198,22 +198,13 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); - // Mock GetDefaultCollectionSemaphoresAsync to return empty set (no existing collections) - collectionRepository - .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) - .Returns(new HashSet()); - var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - // Assert - await collectionRepository - .Received(1) - .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); - + // Assert - Should call with all user IDs (repository does internal filtering) await collectionRepository .Received(1) .CreateDefaultCollectionsBulkAsync( @@ -224,7 +215,7 @@ await collectionRepository [Theory] [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] - public async Task ExecuteSideEffectsAsync_FiltersOutUsersWithExistingCollections( + public async Task ExecuteSideEffectsAsync_PassesAllUsers_RepositoryFiltersInternally( Policy postUpdatedPolicy, Policy? previousPolicyState, [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, @@ -241,34 +232,24 @@ public async Task ExecuteSideEffectsAsync_FiltersOutUsersWithExistingCollections var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); - // Mock GetDefaultCollectionSemaphoresAsync to return one existing user - var existingUserId = orgPolicyDetailsList[0].OrganizationUserId; - collectionRepository - .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) - .Returns([existingUserId]); - var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - // Assert - Should filter out the existing user - await collectionRepository - .Received(1) - .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); - + // Assert - Should pass all user IDs (repository does internal filtering) await collectionRepository .Received(1) .CreateDefaultCollectionsBulkAsync( policyUpdate.OrganizationId, - Arg.Is>(ids => ids.Count() == 2 && !ids.Contains(existingUserId)), + Arg.Is>(ids => ids.Count() == 3), _defaultUserCollectionName); } [Theory] [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] - public async Task ExecuteSideEffectsAsync_DoesNotCallRepository_WhenAllUsersHaveExistingCollections( + public async Task ExecuteSideEffectsAsync_CallsRepositoryWithAllUsers_EvenIfAllHaveCollections( Policy postUpdatedPolicy, Policy? previousPolicyState, [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, @@ -285,26 +266,19 @@ public async Task ExecuteSideEffectsAsync_DoesNotCallRepository_WhenAllUsersHave var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); - // Mock GetDefaultCollectionSemaphoresAsync to return all users - var allUserIds = orgPolicyDetailsList.Select(p => p.OrganizationUserId).ToHashSet(); - collectionRepository - .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) - .Returns(allUserIds); - var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - // Assert - Should not call CreateDefaultCollectionsBulkAsync when all users already have collections + // Assert - Should call repository with all user IDs (repository filters internally) await collectionRepository .Received(1) - .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); - - await collectionRepository - .DidNotReceive() - .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateDefaultCollectionsBulkAsync( + policyUpdate.OrganizationId, + Arg.Is>(ids => ids.Count() == 3), + _defaultUserCollectionName); } private static IEnumerable WhenDefaultCollectionsDoesNotExistTestCases() @@ -497,22 +471,13 @@ public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsert var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); var collectionRepository = Substitute.For(); - // Mock GetDefaultCollectionSemaphoresAsync to return empty set (no existing collections) - collectionRepository - .GetDefaultCollectionSemaphoresAsync(Arg.Any>()) - .Returns(new HashSet()); - var sut = ArrangeSut(factory, policyRepository, collectionRepository); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - // Assert - await collectionRepository - .Received(1) - .GetDefaultCollectionSemaphoresAsync(Arg.Is>(ids => ids.Count() == 3)); - + // Assert - Should call with all user IDs (repository does internal filtering) await collectionRepository .Received(1) .CreateDefaultCollectionsBulkAsync( diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs index b841681cf9df..a1ac02e1ec18 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs @@ -1,5 +1,4 @@  -using Bit.Core.AdminConsole.Collections; using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Enums; @@ -33,13 +32,12 @@ public async Task CreateDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_WhenCallerFiltersExisting( + public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AutoFiltersExisting( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -66,20 +64,17 @@ await CreateUserForOrgAsync(userRepository, organizationUserRepository, organiza var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers); var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); - // Act - Caller filters out existing users (new pattern) - var existingSemaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(affectedOrgUserIds); - var usersNeedingCollections = affectedOrgUserIds.Except(existingSemaphores).ToList(); - await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, usersNeedingCollections, defaultCollectionName); + // Act - Pass all user IDs, method should auto-filter existing users + await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); // Assert - All users now have exactly one collection await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenUsersAlreadyHaveOne( + public async Task CreateDefaultCollectionsBulkAsync_DoesNotCreateDuplicates_WhenUsersAlreadyHaveOne( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -98,19 +93,17 @@ public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenUsersAlr await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); - // Act - Try to create again, should throw specific duplicate collection exception - await Assert.ThrowsAsync(() => - collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName)); + // Act - Try to create again, should silently filter and not create duplicates + await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); - // Assert - Original collections should remain unchanged + // Assert - Original collections should remain unchanged, still only one per user await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - await AssertSempahoresCreatedAsync(collectionRepository, affectedOrgUserIds); await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenDuplicatesNotFiltered( + public async Task CreateDefaultCollectionsBulkAsync_AutoFilters_WhenMixedUsersProvided( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -126,24 +119,24 @@ public async Task CreateDefaultCollectionsBulkAsync_ThrowsException_WhenDuplicat // Create collection for existing user await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, [existingUser.Id], defaultCollectionName); - // Act - Try to create for both without filtering (incorrect usage) - await Assert.ThrowsAsync(() => - collectionRepository.CreateDefaultCollectionsBulkAsync( - organization.Id, - [existingUser.Id, newUser.Id], - defaultCollectionName)); + // Act - Pass both users, method should auto-filter and only create for new user + await collectionRepository.CreateDefaultCollectionsBulkAsync( + organization.Id, + [existingUser.Id, newUser.Id], + defaultCollectionName); - // Assert - Verify existing user still has collection + // Assert - Verify existing user still has exactly one collection var existingUserCollections = await collectionRepository.GetManyByUserIdAsync(existingUser.UserId!.Value); - var existingUserDefaultCollection = existingUserCollections - .SingleOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); - Assert.NotNull(existingUserDefaultCollection); + var existingUserDefaultCollections = existingUserCollections + .Where(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection) + .ToList(); + Assert.Single(existingUserDefaultCollections); - // Verify new user does NOT have collection (transaction rolled back) + // Verify new user now has collection (was created) var newUserCollections = await collectionRepository.GetManyByUserIdAsync(newUser.UserId!.Value); var newUserDefaultCollection = newUserCollections - .FirstOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); - Assert.Null(newUserDefaultCollection); + .SingleOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); + Assert.NotNull(newUserDefaultCollection); await CleanupAsync(organizationRepository, userRepository, organization, [existingUser, newUser]); } @@ -181,14 +174,6 @@ private static async Task CreateUserForOrgAsync(IUserRepositor return orgUser; } - private static async Task AssertSempahoresCreatedAsync(ICollectionRepository collectionRepository, - IEnumerable organizationUserIds) - { - var organizationUserIdHashSet = organizationUserIds.ToHashSet(); - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync(organizationUserIdHashSet); - Assert.Equal(organizationUserIdHashSet, semaphores); - } - private static async Task CleanupAsync(IOrganizationRepository organizationRepository, IUserRepository userRepository, Organization organization, diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 224a9bdc29ab..c778b24fd408 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -1,5 +1,4 @@ -using Bit.Core.AdminConsole.Collections; -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Repositories; using Xunit; @@ -41,9 +40,6 @@ await collectionRepository.CreateDefaultCollectionsAsync( Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Item1.Name)); Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.Item1.OrganizationId)); - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser1.Id, orgUser2.Id]); - Assert.Equal([orgUser1.Id, orgUser2.Id], semaphores); - // Verify each user has exactly 1 collection with correct permissions var orgUser1Collection = Assert.Single(defaultCollections, c => c.Item2.Users.FirstOrDefault()?.Id == orgUser1.Id); @@ -71,7 +67,7 @@ await collectionRepository.CreateDefaultCollectionsAsync( /// Test that calling CreateDefaultCollectionsAsync multiple times does NOT create duplicates /// [Theory, DatabaseData] - public async Task CreateDefaultCollectionsAsync_CalledMultipleTimesForSameOrganizationUser_Throws( + public async Task CreateDefaultCollectionsAsync_CalledMultipleTimesForSameOrganizationUser_DoesNotCreateDuplicates( IUserRepository userRepository, IOrganizationRepository organizationRepository, ICollectionRepository collectionRepository, @@ -88,12 +84,11 @@ await collectionRepository.CreateDefaultCollectionsAsync( [orgUser.Id], "My Items"); - // Second call should throw specific exception and should not create duplicate - await Assert.ThrowsAsync(() => - collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser.Id], - "My Items Duplicate")); + // Second call should silently filter and not create duplicate + await collectionRepository.CreateDefaultCollectionsAsync( + organization.Id, + [orgUser.Id], + "My Items Duplicate"); // Assert - Only one collection should exist var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); @@ -101,9 +96,6 @@ await Assert.ThrowsAsync(() => Assert.Single(defaultCollections); - var semaphores = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); - Assert.Equal([orgUser.Id], semaphores); - var access = await collectionRepository.GetManyUsersByIdAsync(defaultCollections.Single().Id); var userAccess = Assert.Single(access); Assert.Equal(orgUser.Id, userAccess.Id); diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs deleted file mode 100644 index 39cd1d775cfe..000000000000 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/DefaultCollectionSemaphoreTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Bit.Core.Repositories; -using Xunit; - -namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; - -/// -/// Tests for DefaultCollectionSemaphore table behavior including cascade deletions -/// -public class DefaultCollectionSemaphoreTests -{ - [Theory, DatabaseData] - public async Task DeleteOrganizationUser_CascadeDeletesSemaphore( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository) - { - // Arrange - var user = await userRepository.CreateTestUserAsync(); - var organization = await organizationRepository.CreateTestOrganizationAsync(); - var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser.Id], - "My Items"); - - // Verify semaphore exists - var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); - Assert.Single(semaphoreBefore, s => s == orgUser.Id); - - // Act - Delete organization user - await organizationUserRepository.DeleteAsync(orgUser); - - // Assert - Semaphore should be cascade deleted - var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); - Assert.Empty(semaphoreAfter); - } - - /// - /// Test that deleting an Organization cascades through OrganizationUser to DefaultCollectionSemaphore - /// Note: Cascade path is Organization -> OrganizationUser -> DefaultCollectionSemaphore (not direct) - /// - [Theory, DatabaseData] - public async Task DeleteOrganization_CascadeDeletesSemaphore_ThroughOrganizationUser( - IUserRepository userRepository, - IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository) - { - // Arrange - var user = await userRepository.CreateTestUserAsync(); - var organization = await organizationRepository.CreateTestOrganizationAsync(); - var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser.Id], - "My Items"); - - // Verify semaphore exists - var semaphoreBefore = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); - Assert.Single(semaphoreBefore, s => s == orgUser.Id); - - // Act - Delete organization (which cascades to OrganizationUser, which cascades to semaphore) - await organizationRepository.DeleteAsync(organization); - - // Assert - Semaphore should be cascade deleted via OrganizationUser - var semaphoreAfter = await collectionRepository.GetDefaultCollectionSemaphoresAsync([orgUser.Id]); - Assert.Empty(semaphoreAfter); - } -} diff --git a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql deleted file mode 100644 index 285c39e77dbd..000000000000 --- a/util/Migrator/DbScripts/2025-12-30_00_DefaultCollectionSemaphore.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Create DefaultCollectionSemaphore table --- Cascade behavior: Organization -> OrganizationUser (CASCADE) -> DefaultCollectionSemaphore (CASCADE) --- OrganizationId FK has NO ACTION to avoid competing cascade paths -IF OBJECT_ID('[dbo].[DefaultCollectionSemaphore]') IS NULL -BEGIN - CREATE TABLE [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationUserId] UNIQUEIDENTIFIER NOT NULL, - [CreationDate] DATETIME2 (7) NOT NULL, - CONSTRAINT [PK_DefaultCollectionSemaphore] PRIMARY KEY CLUSTERED ([OrganizationUserId] ASC), - CONSTRAINT [FK_DefaultCollectionSemaphore_OrganizationUser] FOREIGN KEY ([OrganizationUserId]) - REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE CASCADE - ); -END -GO - --- Create stored procedure to read semaphores by OrganizationUserId -CREATE OR ALTER PROCEDURE [dbo].[DefaultCollectionSemaphore_ReadByOrganizationUserIds] - @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY -AS -BEGIN - SET NOCOUNT ON - - SELECT - [OrganizationUserId] - FROM - [dbo].[DefaultCollectionSemaphore] DCS - INNER JOIN - @OrganizationUserIds OU ON [OU].[Id] = [DCS].[OrganizationUserId] -END -GO diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql index a03040c5716d..7afee3e41259 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql +++ b/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql @@ -1,6 +1,6 @@ -- Creates default user collections for organization users --- Uses semaphore table to prevent duplicate default collections at database level --- NOTE: this MUST be executed in a single transaction to obtain semaphore protection +-- Filters out existing default collections at database level +-- NOTE: this MUST be executed in a single transaction to ensure consistency CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), @@ -11,20 +11,20 @@ BEGIN DECLARE @Now DATETIME2(7) = GETUTCDATE() - -- Insert semaphore entries first to obtain the "lock" - -- If this fails due to duplicate key, the entire transaction will be rolled back - INSERT INTO [dbo].[DefaultCollectionSemaphore] - ( - [OrganizationUserId], - [CreationDate] - ) - SELECT - ids.[Id1], -- OrganizationUserId - @Now - FROM - @OrganizationUserCollectionIds ids; + -- Filter to only users who don't have default collections + SELECT ids.Id1, ids.Id2 + INTO #FilteredIds + FROM @OrganizationUserCollectionIds ids + WHERE NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionUser] cu + INNER JOIN [dbo].[Collection] c ON c.Id = cu.CollectionId + WHERE c.OrganizationId = @OrganizationId + AND c.[Type] = 1 -- CollectionType.DefaultUserCollection + AND cu.OrganizationUserId = ids.Id1 + ); - -- Insert collections for users who obtained semaphore entries + -- Insert collections only for users who don't have default collections yet INSERT INTO [dbo].[Collection] ( [Id], @@ -37,7 +37,7 @@ BEGIN [DefaultUserCollectionEmail] ) SELECT - ids.[Id2], -- CollectionId + ids.Id2, -- CollectionId @OrganizationId, @DefaultCollectionName, @Now, @@ -46,7 +46,7 @@ BEGIN NULL, NULL FROM - @OrganizationUserCollectionIds ids; + #FilteredIds ids; -- Insert collection user mappings INSERT INTO [dbo].[CollectionUser] @@ -58,12 +58,14 @@ BEGIN [Manage] ) SELECT - ids.[Id2], -- CollectionId - ids.[Id1], -- OrganizationUserId + ids.Id2, -- CollectionId + ids.Id1, -- OrganizationUserId 0, -- ReadOnly = false 0, -- HidePasswords = false 1 -- Manage = true FROM - @OrganizationUserCollectionIds ids; + #FilteredIds ids; + + DROP TABLE #FilteredIds; END GO diff --git a/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql b/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql deleted file mode 100644 index b8518b0eba8b..000000000000 --- a/util/Migrator/DbScripts/2025-12-30_02_PopulateDefaultCollectionSemaphore.sql +++ /dev/null @@ -1,26 +0,0 @@ --- Populate DefaultCollectionSemaphore from existing Type=1 (DefaultUserCollection) collections --- This migration is idempotent and can be run multiple times safely -INSERT INTO [dbo].[DefaultCollectionSemaphore] -( - [OrganizationUserId], - [CreationDate] -) -SELECT DISTINCT - cu.[OrganizationUserId], - GETUTCDATE() -FROM - [dbo].[Collection] c -INNER JOIN - [dbo].[CollectionUser] cu ON c.[Id] = cu.[CollectionId] -WHERE - c.[Type] = 1 -- CollectionType.DefaultUserCollection - AND NOT EXISTS - ( - SELECT - 1 - FROM - [dbo].[DefaultCollectionSemaphore] dcs - WHERE - dcs.[OrganizationUserId] = cu.[OrganizationUserId] - ); -GO diff --git a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs deleted file mode 100644 index 6d55cbf7653b..000000000000 --- a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.Designer.cs +++ /dev/null @@ -1,3467 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Bit.MySqlMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20251231233256_DefaultCollectionSemaphore")] - partial class DefaultCollectionSemaphore - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("CollectionName") - .HasColumnType("longtext"); - - b.Property("Email") - .HasColumnType("longtext"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("GroupName") - .HasColumnType("longtext"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.Property("ResetPasswordKey") - .HasColumnType("longtext"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("UserGuid") - .HasColumnType("char(36)"); - - b.Property("UserName") - .HasColumnType("longtext"); - - b.Property("UsesKeyConnector") - .HasColumnType("tinyint(1)"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("tinyint(1)") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("varchar(2)"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("LimitCollectionCreation") - .HasColumnType("tinyint(1)"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("tinyint(1)"); - - b.Property("LimitItemDeletion") - .HasColumnType("tinyint(1)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("int"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("int"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("int"); - - b.Property("MaxCollections") - .HasColumnType("smallint"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("datetime(6)"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("PrivateKey") - .HasColumnType("longtext"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("ReferenceData") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Seats") - .HasColumnType("int"); - - b.Property("SelfHost") - .HasColumnType("tinyint(1)"); - - b.Property("SmSeats") - .HasColumnType("int"); - - b.Property("SmServiceAccounts") - .HasColumnType("int"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("SyncSeats") - .HasColumnType("tinyint(1)"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("Use2fa") - .HasColumnType("tinyint(1)"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("tinyint(1)"); - - b.Property("UseApi") - .HasColumnType("tinyint(1)"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("tinyint(1)"); - - b.Property("UseCustomPermissions") - .HasColumnType("tinyint(1)"); - - b.Property("UseDirectory") - .HasColumnType("tinyint(1)"); - - b.Property("UseEvents") - .HasColumnType("tinyint(1)"); - - b.Property("UseGroups") - .HasColumnType("tinyint(1)"); - - b.Property("UseKeyConnector") - .HasColumnType("tinyint(1)"); - - b.Property("UseOrganizationDomains") - .HasColumnType("tinyint(1)"); - - b.Property("UsePasswordManager") - .HasColumnType("tinyint(1)"); - - b.Property("UsePhishingBlocker") - .HasColumnType("tinyint(1)"); - - b.Property("UsePolicies") - .HasColumnType("tinyint(1)"); - - b.Property("UseResetPassword") - .HasColumnType("tinyint(1)"); - - b.Property("UseRiskInsights") - .HasColumnType("tinyint(1)"); - - b.Property("UseScim") - .HasColumnType("tinyint(1)"); - - b.Property("UseSecretsManager") - .HasColumnType("tinyint(1)"); - - b.Property("UseSso") - .HasColumnType("tinyint(1)"); - - b.Property("UseTotp") - .HasColumnType("tinyint(1)"); - - b.Property("UsersGetPremium") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled") - .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("BillingEmail") - .HasColumnType("longtext"); - - b.Property("BillingPhone") - .HasColumnType("longtext"); - - b.Property("BusinessAddress1") - .HasColumnType("longtext"); - - b.Property("BusinessAddress2") - .HasColumnType("longtext"); - - b.Property("BusinessAddress3") - .HasColumnType("longtext"); - - b.Property("BusinessCountry") - .HasColumnType("longtext"); - - b.Property("BusinessName") - .HasColumnType("longtext"); - - b.Property("BusinessTaxNumber") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DiscountId") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasColumnType("longtext"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("longtext"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UseEvents") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Settings") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasColumnType("longtext"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("Permissions") - .HasColumnType("longtext"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("varchar(25)"); - - b.Property("Approved") - .HasColumnType("tinyint(1)"); - - b.Property("AuthenticationDate") - .HasColumnType("datetime(6)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("MasterPasswordHash") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("RequestDeviceType") - .HasColumnType("tinyint unsigned"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("ResponseDate") - .HasColumnType("datetime(6)"); - - b.Property("ResponseDeviceId") - .HasColumnType("char(36)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("GranteeId") - .HasColumnType("char(36)"); - - b.Property("GrantorId") - .HasColumnType("char(36)"); - - b.Property("KeyEncrypted") - .HasColumnType("longtext"); - - b.Property("LastNotificationDate") - .HasColumnType("datetime(6)"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("WaitTimeDays") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("GranteeId"); - - b.HasIndex("GrantorId"); - - b.ToTable("EmergencyAccess", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("ConsumedDate") - .HasColumnType("datetime(6)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AaGuid") - .HasColumnType("char(36)"); - - b.Property("Counter") - .HasColumnType("int"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("varchar(2000)"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SupportsPrf") - .HasColumnType("tinyint(1)"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("varchar(20)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("int"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Seats") - .HasColumnType("int"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("InstallationId") - .HasColumnType("char(36)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AssignedSeats") - .HasColumnType("int"); - - b.Property("ClientId") - .HasColumnType("char(36)"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Created") - .HasColumnType("datetime(6)"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Total") - .HasColumnType("decimal(65,30)"); - - b.Property("UsedSeats") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AllocatedSeats") - .HasColumnType("int"); - - b.Property("PlanType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("PurchasedSeats") - .HasColumnType("int"); - - b.Property("SeatMinimum") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("int"); - - b.Property("ApplicationCount") - .HasColumnType("int"); - - b.Property("ApplicationData") - .HasColumnType("longtext"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalApplicationCount") - .HasColumnType("int"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalMemberCount") - .HasColumnType("int"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("int"); - - b.Property("CriticalPasswordCount") - .HasColumnType("int"); - - b.Property("MemberAtRiskCount") - .HasColumnType("int"); - - b.Property("MemberCount") - .HasColumnType("int"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("int"); - - b.Property("PasswordCount") - .HasColumnType("int"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SummaryData") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Uri") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("varchar(449)"); - - b.Property("AbsoluteExpiration") - .HasColumnType("datetime(6)"); - - b.Property("ExpiresAtTime") - .HasColumnType("datetime(6)"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("bigint"); - - b.Property("Value") - .IsRequired() - .HasColumnType("longblob"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("longtext"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("HidePasswords") - .HasColumnType("tinyint(1)"); - - b.Property("Manage") - .HasColumnType("tinyint(1)"); - - b.Property("ReadOnly") - .HasColumnType("tinyint(1)"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("char(36)"); - - b.Property("Active") - .HasColumnType("tinyint(1)") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("longtext"); - - b.Property("EncryptedPublicKey") - .HasColumnType("longtext"); - - b.Property("EncryptedUserKey") - .HasColumnType("longtext"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("varchar(255)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ActingUserId") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CollectionId") - .HasColumnType("char(36)"); - - b.Property("Date") - .HasColumnType("datetime(6)"); - - b.Property("DeviceType") - .HasColumnType("tinyint unsigned"); - - b.Property("DomainName") - .HasColumnType("longtext"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("InstallationId") - .HasColumnType("char(36)"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("PolicyId") - .HasColumnType("char(36)"); - - b.Property("ProjectId") - .HasColumnType("char(36)"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("ProviderOrganizationId") - .HasColumnType("char(36)"); - - b.Property("ProviderUserId") - .HasColumnType("char(36)"); - - b.Property("SecretId") - .HasColumnType("char(36)"); - - b.Property("ServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("SystemUser") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("char(36)"); - - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Config") - .HasColumnType("longtext"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("varchar(255)"); - - b.Property("JobRunCount") - .HasColumnType("int"); - - b.Property("LastCheckedDate") - .HasColumnType("datetime(6)"); - - b.Property("NextRunDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("VerifiedDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("IsAdminInitiated") - .HasColumnType("tinyint(1)"); - - b.Property("LastSyncDate") - .HasColumnType("datetime(6)"); - - b.Property("Notes") - .HasColumnType("longtext"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("PlanSponsorshipType") - .HasColumnType("tinyint unsigned"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("char(36)"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("char(36)"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("ToDelete") - .HasColumnType("tinyint(1)"); - - b.Property("ValidUntil") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessSecretsManager") - .HasColumnType("tinyint(1)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Permissions") - .HasColumnType("longtext"); - - b.Property("ResetPasswordKey") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccessCount") - .HasColumnType("int"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("DeletionDate") - .HasColumnType("datetime(6)"); - - b.Property("Disabled") - .HasColumnType("tinyint(1)"); - - b.Property("Emails") - .HasMaxLength(1024) - .HasColumnType("varchar(1024)"); - - b.Property("ExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("HideEmail") - .HasColumnType("tinyint(1)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("MaxAccessCount") - .HasColumnType("int"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("varchar(40)"); - - b.Property("Active") - .HasColumnType("tinyint(1)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar(10)"); - - b.Property("Rate") - .HasColumnType("decimal(65,30)"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("varchar(2)"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Amount") - .HasColumnType("decimal(65,30)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("PaymentMethodType") - .HasColumnType("tinyint unsigned"); - - b.Property("ProviderId") - .HasColumnType("char(36)"); - - b.Property("Refunded") - .HasColumnType("tinyint(1)"); - - b.Property("RefundedAmount") - .HasColumnType("decimal(65,30)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("AccountRevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("varchar(30)"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("varchar(7)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("varchar(10)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("EmailVerified") - .HasColumnType("tinyint(1)"); - - b.Property("EquivalentDomains") - .HasColumnType("longtext"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("longtext"); - - b.Property("FailedLoginCount") - .HasColumnType("int"); - - b.Property("ForcePasswordReset") - .HasColumnType("tinyint(1)"); - - b.Property("Gateway") - .HasColumnType("tinyint unsigned"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Kdf") - .HasColumnType("tinyint unsigned"); - - b.Property("KdfIterations") - .HasColumnType("int"); - - b.Property("KdfMemory") - .HasColumnType("int"); - - b.Property("KdfParallelism") - .HasColumnType("int"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("LastEmailChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LastFailedLoginDate") - .HasColumnType("datetime(6)"); - - b.Property("LastKdfChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LastKeyRotationDate") - .HasColumnType("datetime(6)"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("datetime(6)"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("varchar(100)"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("varchar(300)"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("Premium") - .HasColumnType("tinyint(1)"); - - b.Property("PremiumExpirationDate") - .HasColumnType("datetime(6)"); - - b.Property("PrivateKey") - .HasColumnType("longtext"); - - b.Property("PublicKey") - .HasColumnType("longtext"); - - b.Property("ReferenceData") - .HasColumnType("longtext"); - - b.Property("RenewalReminderDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("SecurityState") - .HasColumnType("longtext"); - - b.Property("SecurityVersion") - .HasColumnType("int"); - - b.Property("SignedPublicKey") - .HasColumnType("longtext"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("TwoFactorProviders") - .HasColumnType("longtext"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("varchar(32)"); - - b.Property("UsesKeyConnector") - .HasColumnType("tinyint(1)"); - - b.Property("VerifyDevices") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("SignatureAlgorithm") - .HasColumnType("tinyint unsigned"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("varchar(3000)"); - - b.Property("ClientType") - .HasColumnType("tinyint unsigned"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Global") - .HasColumnType("tinyint(1)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Priority") - .HasColumnType("tinyint unsigned"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("TaskId") - .HasColumnType("char(36)"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("char(36)"); - - b.Property("NotificationId") - .HasColumnType("char(36)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("ReadDate") - .HasColumnType("datetime(6)"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("varchar(150)"); - - b.Property("LastActivityDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("varchar(34)"); - - b.Property("Read") - .HasColumnType("tinyint(1)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Write") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("varchar(128)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("varchar(4000)"); - - b.Property("ExpireAt") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("varchar(200)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("varchar(4000)"); - - b.Property("ServiceAccountId") - .HasColumnType("char(36)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("Note") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Value") - .HasColumnType("longtext"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("EditorServiceAccountId") - .HasColumnType("char(36)"); - - b.Property("SecretId") - .HasColumnType("char(36)"); - - b.Property("Value") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("VersionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("ArchivedDate") - .HasColumnType("datetime(6)"); - - b.Property("Attachments") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Data") - .HasColumnType("longtext"); - - b.Property("DeletedDate") - .HasColumnType("datetime(6)"); - - b.Property("Favorites") - .HasColumnType("longtext"); - - b.Property("Folders") - .HasColumnType("longtext"); - - b.Property("Key") - .HasColumnType("longtext"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("Reprompt") - .HasColumnType("tinyint unsigned"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("UserId") - .HasColumnType("char(36)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CipherId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Status") - .HasColumnType("tinyint unsigned"); - - b.Property("Type") - .HasColumnType("tinyint unsigned"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("char(36)"); - - b.Property("SecretsId") - .HasColumnType("char(36)"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("char(36)") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs b/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs deleted file mode 100644 index d73823066212..000000000000 --- a/util/MySqlMigrations/Migrations/20251231233256_DefaultCollectionSemaphore.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.MySqlMigrations.Migrations; - -/// -public partial class DefaultCollectionSemaphore : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), - CreationDate = table.Column(type: "datetime(6)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUser~", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }) - .Annotation("MySql:CharSet", "utf8mb4"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } -} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 891decef79f8..53fccb37a09f 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -69,19 +69,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2617,17 +2604,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs deleted file mode 100644 index 0f1d0cc9db2a..000000000000 --- a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.Designer.cs +++ /dev/null @@ -1,3473 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Bit.PostgresMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20251231233251_DefaultCollectionSemaphore")] - partial class DefaultCollectionSemaphore - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") - .HasAnnotation("ProductVersion", "8.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("CollectionName") - .HasColumnType("text"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("GroupName") - .HasColumnType("text"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.Property("ResetPasswordKey") - .HasColumnType("text"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("UserGuid") - .HasColumnType("uuid"); - - b.Property("UserName") - .HasColumnType("text"); - - b.Property("UsesKeyConnector") - .HasColumnType("boolean"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("LimitCollectionCreation") - .HasColumnType("boolean"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("boolean"); - - b.Property("LimitItemDeletion") - .HasColumnType("boolean"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("integer"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("integer"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("integer"); - - b.Property("MaxCollections") - .HasColumnType("smallint"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("timestamp with time zone"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("PrivateKey") - .HasColumnType("text"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("ReferenceData") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Seats") - .HasColumnType("integer"); - - b.Property("SelfHost") - .HasColumnType("boolean"); - - b.Property("SmSeats") - .HasColumnType("integer"); - - b.Property("SmServiceAccounts") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("SyncSeats") - .HasColumnType("boolean"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("Use2fa") - .HasColumnType("boolean"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("boolean"); - - b.Property("UseApi") - .HasColumnType("boolean"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("boolean"); - - b.Property("UseCustomPermissions") - .HasColumnType("boolean"); - - b.Property("UseDirectory") - .HasColumnType("boolean"); - - b.Property("UseEvents") - .HasColumnType("boolean"); - - b.Property("UseGroups") - .HasColumnType("boolean"); - - b.Property("UseKeyConnector") - .HasColumnType("boolean"); - - b.Property("UseOrganizationDomains") - .HasColumnType("boolean"); - - b.Property("UsePasswordManager") - .HasColumnType("boolean"); - - b.Property("UsePhishingBlocker") - .HasColumnType("boolean"); - - b.Property("UsePolicies") - .HasColumnType("boolean"); - - b.Property("UseResetPassword") - .HasColumnType("boolean"); - - b.Property("UseRiskInsights") - .HasColumnType("boolean"); - - b.Property("UseScim") - .HasColumnType("boolean"); - - b.Property("UseSecretsManager") - .HasColumnType("boolean"); - - b.Property("UseSso") - .HasColumnType("boolean"); - - b.Property("UseTotp") - .HasColumnType("boolean"); - - b.Property("UsersGetPremium") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled"); - - NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("BillingEmail") - .HasColumnType("text"); - - b.Property("BillingPhone") - .HasColumnType("text"); - - b.Property("BusinessAddress1") - .HasColumnType("text"); - - b.Property("BusinessAddress2") - .HasColumnType("text"); - - b.Property("BusinessAddress3") - .HasColumnType("text"); - - b.Property("BusinessCountry") - .HasColumnType("text"); - - b.Property("BusinessName") - .HasColumnType("text"); - - b.Property("BusinessTaxNumber") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DiscountId") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasColumnType("text"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UseEvents") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Settings") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Permissions") - .HasColumnType("text"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("character varying(25)"); - - b.Property("Approved") - .HasColumnType("boolean"); - - b.Property("AuthenticationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("MasterPasswordHash") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("RequestDeviceType") - .HasColumnType("smallint"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ResponseDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ResponseDeviceId") - .HasColumnType("uuid"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("GranteeId") - .HasColumnType("uuid"); - - b.Property("GrantorId") - .HasColumnType("uuid"); - - b.Property("KeyEncrypted") - .HasColumnType("text"); - - b.Property("LastNotificationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("WaitTimeDays") - .HasColumnType("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"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ConsumedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AaGuid") - .HasColumnType("uuid"); - - b.Property("Counter") - .HasColumnType("integer"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SupportsPrf") - .HasColumnType("boolean"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("integer"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Seats") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("InstallationId") - .HasColumnType("uuid"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AssignedSeats") - .HasColumnType("integer"); - - b.Property("ClientId") - .HasColumnType("uuid"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Total") - .HasColumnType("numeric"); - - b.Property("UsedSeats") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AllocatedSeats") - .HasColumnType("integer"); - - b.Property("PlanType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("PurchasedSeats") - .HasColumnType("integer"); - - b.Property("SeatMinimum") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("integer"); - - b.Property("ApplicationCount") - .HasColumnType("integer"); - - b.Property("ApplicationData") - .HasColumnType("text"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalApplicationCount") - .HasColumnType("integer"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalMemberCount") - .HasColumnType("integer"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("integer"); - - b.Property("CriticalPasswordCount") - .HasColumnType("integer"); - - b.Property("MemberAtRiskCount") - .HasColumnType("integer"); - - b.Property("MemberCount") - .HasColumnType("integer"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("integer"); - - b.Property("PasswordCount") - .HasColumnType("integer"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SummaryData") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Uri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("character varying(449)"); - - b.Property("AbsoluteExpiration") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpiresAtTime") - .HasColumnType("timestamp with time zone"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("bigint"); - - b.Property("Value") - .IsRequired() - .HasColumnType("bytea"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("text"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("HidePasswords") - .HasColumnType("boolean"); - - b.Property("Manage") - .HasColumnType("boolean"); - - b.Property("ReadOnly") - .HasColumnType("boolean"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("text"); - - b.Property("EncryptedPublicKey") - .HasColumnType("text"); - - b.Property("EncryptedUserKey") - .HasColumnType("text"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ActingUserId") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CollectionId") - .HasColumnType("uuid"); - - b.Property("Date") - .HasColumnType("timestamp with time zone"); - - b.Property("DeviceType") - .HasColumnType("smallint"); - - b.Property("DomainName") - .HasColumnType("text"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("uuid"); - - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("InstallationId") - .HasColumnType("uuid"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("PolicyId") - .HasColumnType("uuid"); - - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("ProviderOrganizationId") - .HasColumnType("uuid"); - - b.Property("ProviderUserId") - .HasColumnType("uuid"); - - b.Property("SecretId") - .HasColumnType("uuid"); - - b.Property("ServiceAccountId") - .HasColumnType("uuid"); - - b.Property("SystemUser") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("uuid"); - - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Config") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("JobRunCount") - .HasColumnType("integer"); - - b.Property("LastCheckedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("NextRunDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("text"); - - b.Property("VerifiedDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("IsAdminInitiated") - .HasColumnType("boolean"); - - b.Property("LastSyncDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Notes") - .HasColumnType("text"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PlanSponsorshipType") - .HasColumnType("smallint"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("uuid"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("uuid"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("uuid"); - - b.Property("ToDelete") - .HasColumnType("boolean"); - - b.Property("ValidUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessSecretsManager") - .HasColumnType("boolean"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Permissions") - .HasColumnType("text"); - - b.Property("ResetPasswordKey") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccessCount") - .HasColumnType("integer"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("DeletionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Disabled") - .HasColumnType("boolean"); - - b.Property("Emails") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("HideEmail") - .HasColumnType("boolean"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("MaxAccessCount") - .HasColumnType("integer"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("Rate") - .HasColumnType("numeric"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Amount") - .HasColumnType("numeric"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("PaymentMethodType") - .HasColumnType("smallint"); - - b.Property("ProviderId") - .HasColumnType("uuid"); - - b.Property("Refunded") - .HasColumnType("boolean"); - - b.Property("RefundedAmount") - .HasColumnType("numeric"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("AccountRevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("character varying(7)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .UseCollation("postgresIndetermanisticCollation"); - - b.Property("EmailVerified") - .HasColumnType("boolean"); - - b.Property("EquivalentDomains") - .HasColumnType("text"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("text"); - - b.Property("FailedLoginCount") - .HasColumnType("integer"); - - b.Property("ForcePasswordReset") - .HasColumnType("boolean"); - - b.Property("Gateway") - .HasColumnType("smallint"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Kdf") - .HasColumnType("smallint"); - - b.Property("KdfIterations") - .HasColumnType("integer"); - - b.Property("KdfMemory") - .HasColumnType("integer"); - - b.Property("KdfParallelism") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("LastEmailChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastFailedLoginDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastKdfChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastKeyRotationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("MaxStorageGb") - .HasColumnType("smallint"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Premium") - .HasColumnType("boolean"); - - b.Property("PremiumExpirationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivateKey") - .HasColumnType("text"); - - b.Property("PublicKey") - .HasColumnType("text"); - - b.Property("ReferenceData") - .HasColumnType("text"); - - b.Property("RenewalReminderDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("SecurityState") - .HasColumnType("text"); - - b.Property("SecurityVersion") - .HasColumnType("integer"); - - b.Property("SignedPublicKey") - .HasColumnType("text"); - - b.Property("Storage") - .HasColumnType("bigint"); - - b.Property("TwoFactorProviders") - .HasColumnType("text"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("UsesKeyConnector") - .HasColumnType("boolean"); - - b.Property("VerifyDevices") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("SignatureAlgorithm") - .HasColumnType("smallint"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("character varying(3000)"); - - b.Property("ClientType") - .HasColumnType("smallint"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Global") - .HasColumnType("boolean"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Priority") - .HasColumnType("smallint"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("TaskId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("NotificationId") - .HasColumnType("uuid"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ReadDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.Property("LastActivityDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("character varying(34)"); - - b.Property("Read") - .HasColumnType("boolean"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Write") - .HasColumnType("boolean"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.Property("ServiceAccountId") - .HasColumnType("uuid"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Note") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("uuid"); - - b.Property("EditorServiceAccountId") - .HasColumnType("uuid"); - - b.Property("SecretId") - .HasColumnType("uuid"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text"); - - b.Property("VersionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("ArchivedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Attachments") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("DeletedDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Favorites") - .HasColumnType("text"); - - b.Property("Folders") - .HasColumnType("text"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("Reprompt") - .HasColumnType("smallint"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CipherId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Status") - .HasColumnType("smallint"); - - b.Property("Type") - .HasColumnType("smallint"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("uuid"); - - b.Property("SecretsId") - .HasColumnType("uuid"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("uuid") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs b/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs deleted file mode 100644 index 39121813ab8c..000000000000 --- a/util/PostgresMigrations/Migrations/20251231233251_DefaultCollectionSemaphore.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.PostgresMigrations.Migrations; - -/// -public partial class DefaultCollectionSemaphore : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "uuid", nullable: false), - CreationDate = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUse~", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } -} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 43179226f532..6d5715bcecb1 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -70,19 +70,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2623,17 +2610,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs deleted file mode 100644 index 1afaf2b4dd88..000000000000 --- a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.Designer.cs +++ /dev/null @@ -1,3456 +0,0 @@ -// -using System; -using Bit.Infrastructure.EntityFramework.Repositories; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Bit.SqliteMigrations.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20251231233301_DefaultCollectionSemaphore")] - partial class DefaultCollectionSemaphore - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); - - modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => - { - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("CollectionName") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("GroupName") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.Property("ResetPasswordKey") - .HasColumnType("TEXT"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("UserGuid") - .HasColumnType("TEXT"); - - b.Property("UserName") - .HasColumnType("TEXT"); - - b.Property("UsesKeyConnector") - .HasColumnType("INTEGER"); - - b.ToTable("OrganizationMemberBaseDetails"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AllowAdminAccessToAllCollectionItems") - .HasColumnType("INTEGER") - .HasDefaultValue(true); - - b.Property("BillingEmail") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress1") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress2") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessAddress3") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessCountry") - .HasMaxLength(2) - .HasColumnType("TEXT"); - - b.Property("BusinessName") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("BusinessTaxNumber") - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Identifier") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("LimitCollectionCreation") - .HasColumnType("INTEGER"); - - b.Property("LimitCollectionDeletion") - .HasColumnType("INTEGER"); - - b.Property("LimitItemDeletion") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSmSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxAutoscaleSmServiceAccounts") - .HasColumnType("INTEGER"); - - b.Property("MaxCollections") - .HasColumnType("INTEGER"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("TEXT"); - - b.Property("Plan") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("PrivateKey") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("ReferenceData") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Seats") - .HasColumnType("INTEGER"); - - b.Property("SelfHost") - .HasColumnType("INTEGER"); - - b.Property("SmSeats") - .HasColumnType("INTEGER"); - - b.Property("SmServiceAccounts") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Storage") - .HasColumnType("INTEGER"); - - b.Property("SyncSeats") - .HasColumnType("INTEGER"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("Use2fa") - .HasColumnType("INTEGER"); - - b.Property("UseAdminSponsoredFamilies") - .HasColumnType("INTEGER"); - - b.Property("UseApi") - .HasColumnType("INTEGER"); - - b.Property("UseAutomaticUserConfirmation") - .HasColumnType("INTEGER"); - - b.Property("UseCustomPermissions") - .HasColumnType("INTEGER"); - - b.Property("UseDirectory") - .HasColumnType("INTEGER"); - - b.Property("UseEvents") - .HasColumnType("INTEGER"); - - b.Property("UseGroups") - .HasColumnType("INTEGER"); - - b.Property("UseKeyConnector") - .HasColumnType("INTEGER"); - - b.Property("UseOrganizationDomains") - .HasColumnType("INTEGER"); - - b.Property("UsePasswordManager") - .HasColumnType("INTEGER"); - - b.Property("UsePhishingBlocker") - .HasColumnType("INTEGER"); - - b.Property("UsePolicies") - .HasColumnType("INTEGER"); - - b.Property("UseResetPassword") - .HasColumnType("INTEGER"); - - b.Property("UseRiskInsights") - .HasColumnType("INTEGER"); - - b.Property("UseScim") - .HasColumnType("INTEGER"); - - b.Property("UseSecretsManager") - .HasColumnType("INTEGER"); - - b.Property("UseSso") - .HasColumnType("INTEGER"); - - b.Property("UseTotp") - .HasColumnType("INTEGER"); - - b.Property("UsersGetPremium") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Id", "Enabled") - .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); - - b.ToTable("Organization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.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.AdminConsole.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.AdminConsole.Models.Policy", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Policy", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("BillingEmail") - .HasColumnType("TEXT"); - - b.Property("BillingPhone") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress1") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress2") - .HasColumnType("TEXT"); - - b.Property("BusinessAddress3") - .HasColumnType("TEXT"); - - b.Property("BusinessCountry") - .HasColumnType("TEXT"); - - b.Property("BusinessName") - .HasColumnType("TEXT"); - - b.Property("BusinessTaxNumber") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DiscountId") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UseEvents") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Provider", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Settings") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderOrganization", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Permissions") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId"); - - b.ToTable("ProviderUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessCode") - .HasMaxLength(25) - .HasColumnType("TEXT"); - - b.Property("Approved") - .HasColumnType("INTEGER"); - - b.Property("AuthenticationDate") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("MasterPasswordHash") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("RequestCountryName") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("RequestDeviceIdentifier") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("RequestDeviceType") - .HasColumnType("INTEGER"); - - b.Property("RequestIpAddress") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("ResponseDate") - .HasColumnType("TEXT"); - - b.Property("ResponseDeviceId") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ResponseDeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AuthRequest", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("GranteeId") - .HasColumnType("TEXT"); - - b.Property("GrantorId") - .HasColumnType("TEXT"); - - b.Property("KeyEncrypted") - .HasColumnType("TEXT"); - - b.Property("LastNotificationDate") - .HasColumnType("TEXT"); - - b.Property("RecoveryInitiatedDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("WaitTimeDays") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("GranteeId"); - - b.HasIndex("GrantorId"); - - b.ToTable("EmergencyAccess", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("ConsumedDate") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasName("PK_Grant") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpirationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Key") - .IsUnique(); - - b.ToTable("Grant", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("SsoConfig", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId"); - - b.HasIndex("OrganizationId", "ExternalId") - .IsUnique() - .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SsoUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AaGuid") - .HasColumnType("TEXT"); - - b.Property("Counter") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("CredentialId") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EncryptedPrivateKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("EncryptedPublicKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("EncryptedUserKey") - .HasMaxLength(2000) - .HasColumnType("TEXT"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SupportsPrf") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasMaxLength(20) - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("WebAuthnCredential", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("GatewayCustomerId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("MaxAutoscaleSeats") - .HasColumnType("INTEGER"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Seats") - .HasColumnType("INTEGER"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId", "OrganizationId") - .IsUnique(); - - b.ToTable("ClientOrganizationMigrationRecord", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("InstallationId") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("InstallationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationInstallation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AssignedSeats") - .HasColumnType("INTEGER"); - - b.Property("ClientId") - .HasColumnType("TEXT"); - - b.Property("ClientName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Created") - .HasColumnType("TEXT"); - - b.Property("InvoiceId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("InvoiceNumber") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PlanName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Total") - .HasColumnType("TEXT"); - - b.Property("UsedSeats") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.ToTable("ProviderInvoiceItem", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AllocatedSeats") - .HasColumnType("INTEGER"); - - b.Property("PlanType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("PurchasedSeats") - .HasColumnType("INTEGER"); - - b.Property("SeatMinimum") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderId"); - - b.HasIndex("Id", "PlanType") - .IsUnique(); - - b.ToTable("ProviderPlan", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationReport", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ApplicationAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationCount") - .HasColumnType("INTEGER"); - - b.Property("ApplicationData") - .HasColumnType("TEXT"); - - b.Property("ContentEncryptionKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("CriticalApplicationAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalApplicationCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalMemberAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalMemberCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalPasswordAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("CriticalPasswordCount") - .HasColumnType("INTEGER"); - - b.Property("MemberAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("MemberCount") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PasswordAtRiskCount") - .HasColumnType("INTEGER"); - - b.Property("PasswordCount") - .HasColumnType("INTEGER"); - - b.Property("ReportData") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SummaryData") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationReport", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Uri") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("PasswordHealthReportApplication", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => - { - b.Property("Id") - .HasMaxLength(449) - .HasColumnType("TEXT"); - - b.Property("AbsoluteExpiration") - .HasColumnType("TEXT"); - - b.Property("ExpiresAtTime") - .HasColumnType("TEXT"); - - b.Property("SlidingExpirationInSeconds") - .HasColumnType("INTEGER"); - - b.Property("Value") - .IsRequired() - .HasColumnType("BLOB"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ExpiresAtTime") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Cache", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DefaultUserCollectionEmail") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Collection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.HasKey("CollectionId", "CipherId"); - - b.HasIndex("CipherId"); - - b.ToTable("CollectionCipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionId", "GroupId"); - - b.HasIndex("GroupId"); - - b.ToTable("CollectionGroups"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("HidePasswords") - .HasColumnType("INTEGER"); - - b.Property("Manage") - .HasColumnType("INTEGER"); - - b.Property("ReadOnly") - .HasColumnType("INTEGER"); - - b.HasKey("CollectionId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Active") - .HasColumnType("INTEGER") - .HasDefaultValue(true); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EncryptedPrivateKey") - .HasColumnType("TEXT"); - - b.Property("EncryptedPublicKey") - .HasColumnType("TEXT"); - - b.Property("EncryptedUserKey") - .HasColumnType("TEXT"); - - b.Property("Identifier") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PushToken") - .HasMaxLength(255) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Identifier") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "Identifier") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Device", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ActingUserId") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CollectionId") - .HasColumnType("TEXT"); - - b.Property("Date") - .HasColumnType("TEXT"); - - b.Property("DeviceType") - .HasColumnType("INTEGER"); - - b.Property("DomainName") - .HasColumnType("TEXT"); - - b.Property("GrantedServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("InstallationId") - .HasColumnType("TEXT"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("PolicyId") - .HasColumnType("TEXT"); - - b.Property("ProjectId") - .HasColumnType("TEXT"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("ProviderOrganizationId") - .HasColumnType("TEXT"); - - b.Property("ProviderUserId") - .HasColumnType("TEXT"); - - b.Property("SecretId") - .HasColumnType("TEXT"); - - b.Property("ServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("SystemUser") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") - .HasDatabaseName("IX_Event_DateOrganizationIdUserId") - .HasAnnotation("SqlServer:Clustered", false) - .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); - - b.ToTable("Event", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("Group", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.Property("GroupId") - .HasColumnType("TEXT"); - - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.HasKey("GroupId", "OrganizationUserId"); - - b.HasIndex("OrganizationUserId"); - - b.ToTable("GroupUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Config") - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationConnection", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DomainName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("TEXT"); - - b.Property("JobRunCount") - .HasColumnType("INTEGER"); - - b.Property("LastCheckedDate") - .HasColumnType("TEXT"); - - b.Property("NextRunDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Txt") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("VerifiedDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("OrganizationDomain", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("FriendlyName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("IsAdminInitiated") - .HasColumnType("INTEGER"); - - b.Property("LastSyncDate") - .HasColumnType("TEXT"); - - b.Property("Notes") - .HasColumnType("TEXT"); - - b.Property("OfferedToEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PlanSponsorshipType") - .HasColumnType("INTEGER"); - - b.Property("SponsoredOrganizationId") - .HasColumnType("TEXT"); - - b.Property("SponsoringOrganizationId") - .HasColumnType("TEXT"); - - b.Property("SponsoringOrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("ToDelete") - .HasColumnType("INTEGER"); - - b.Property("ValidUntil") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("SponsoredOrganizationId"); - - b.HasIndex("SponsoringOrganizationId"); - - b.HasIndex("SponsoringOrganizationUserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationSponsorship", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessSecretsManager") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("ExternalId") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Permissions") - .HasColumnType("TEXT"); - - b.Property("ResetPasswordKey") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationUser", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessCount") - .HasColumnType("INTEGER"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("DeletionDate") - .HasColumnType("TEXT"); - - b.Property("Disabled") - .HasColumnType("INTEGER"); - - b.Property("Emails") - .HasMaxLength(1024) - .HasColumnType("TEXT"); - - b.Property("ExpirationDate") - .HasColumnType("TEXT"); - - b.Property("HideEmail") - .HasColumnType("INTEGER"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("MaxAccessCount") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Password") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("DeletionDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Send", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => - { - b.Property("Id") - .HasMaxLength(40) - .HasColumnType("TEXT"); - - b.Property("Active") - .HasColumnType("INTEGER"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("TEXT"); - - b.Property("Rate") - .HasColumnType("TEXT"); - - b.Property("State") - .HasMaxLength(2) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TaxRate", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Amount") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Details") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("PaymentMethodType") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .HasColumnType("TEXT"); - - b.Property("Refunded") - .HasColumnType("INTEGER"); - - b.Property("RefundedAmount") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("ProviderId"); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId", "OrganizationId", "CreationDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Transaction", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccountRevisionDate") - .HasColumnType("TEXT"); - - b.Property("ApiKey") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("TEXT"); - - b.Property("AvatarColor") - .HasMaxLength(7) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Culture") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("TEXT"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailVerified") - .HasColumnType("INTEGER"); - - b.Property("EquivalentDomains") - .HasColumnType("TEXT"); - - b.Property("ExcludedGlobalEquivalentDomains") - .HasColumnType("TEXT"); - - b.Property("FailedLoginCount") - .HasColumnType("INTEGER"); - - b.Property("ForcePasswordReset") - .HasColumnType("INTEGER"); - - b.Property("Gateway") - .HasColumnType("INTEGER"); - - b.Property("GatewayCustomerId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("GatewaySubscriptionId") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Kdf") - .HasColumnType("INTEGER"); - - b.Property("KdfIterations") - .HasColumnType("INTEGER"); - - b.Property("KdfMemory") - .HasColumnType("INTEGER"); - - b.Property("KdfParallelism") - .HasColumnType("INTEGER"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("LastEmailChangeDate") - .HasColumnType("TEXT"); - - b.Property("LastFailedLoginDate") - .HasColumnType("TEXT"); - - b.Property("LastKdfChangeDate") - .HasColumnType("TEXT"); - - b.Property("LastKeyRotationDate") - .HasColumnType("TEXT"); - - b.Property("LastPasswordChangeDate") - .HasColumnType("TEXT"); - - b.Property("LicenseKey") - .HasMaxLength(100) - .HasColumnType("TEXT"); - - b.Property("MasterPassword") - .HasMaxLength(300) - .HasColumnType("TEXT"); - - b.Property("MasterPasswordHint") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("MaxStorageGb") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("Premium") - .HasColumnType("INTEGER"); - - b.Property("PremiumExpirationDate") - .HasColumnType("TEXT"); - - b.Property("PrivateKey") - .HasColumnType("TEXT"); - - b.Property("PublicKey") - .HasColumnType("TEXT"); - - b.Property("ReferenceData") - .HasColumnType("TEXT"); - - b.Property("RenewalReminderDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.Property("SecurityState") - .HasColumnType("TEXT"); - - b.Property("SecurityVersion") - .HasColumnType("INTEGER"); - - b.Property("SignedPublicKey") - .HasColumnType("TEXT"); - - b.Property("Storage") - .HasColumnType("INTEGER"); - - b.Property("TwoFactorProviders") - .HasColumnType("TEXT"); - - b.Property("TwoFactorRecoveryCode") - .HasMaxLength(32) - .HasColumnType("TEXT"); - - b.Property("UsesKeyConnector") - .HasColumnType("INTEGER"); - - b.Property("VerifyDevices") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Email") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("User", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("SignatureAlgorithm") - .HasColumnType("INTEGER"); - - b.Property("SigningKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("VerifyingKey") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("UserSignatureKeyPair", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Body") - .HasMaxLength(3000) - .HasColumnType("TEXT"); - - b.Property("ClientType") - .HasColumnType("INTEGER"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Global") - .HasColumnType("INTEGER"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Priority") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("TaskId") - .HasColumnType("TEXT"); - - b.Property("Title") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("TaskId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("UserId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") - .IsDescending(false, false, false, false, true, true) - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Notification", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("NotificationId") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("ReadDate") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "NotificationId") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("NotificationId"); - - b.ToTable("NotificationStatus", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("TEXT"); - - b.Property("LastActivityDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("TEXT"); - - b.Property("Read") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Write") - .HasColumnType("INTEGER"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.ToTable("AccessPolicy", (string)null); - - b.HasDiscriminator().HasValue("AccessPolicy"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ClientSecretHash") - .HasMaxLength(128) - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("TEXT"); - - b.Property("ExpireAt") - .HasColumnType("TEXT"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("TEXT"); - - b.Property("ServiceAccountId") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("ServiceAccountId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ApiKey", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Project", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("Note") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("DeletedDate") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("Secret", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("EditorOrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("EditorServiceAccountId") - .HasColumnType("TEXT"); - - b.Property("SecretId") - .HasColumnType("TEXT"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("VersionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("EditorOrganizationUserId") - .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); - - b.HasIndex("EditorServiceAccountId") - .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); - - b.HasIndex("SecretId") - .HasDatabaseName("IX_SecretVersion_SecretId"); - - b.ToTable("SecretVersion"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("ServiceAccount", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ArchivedDate") - .HasColumnType("TEXT"); - - b.Property("Attachments") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Data") - .HasColumnType("TEXT"); - - b.Property("DeletedDate") - .HasColumnType("TEXT"); - - b.Property("Favorites") - .HasColumnType("TEXT"); - - b.Property("Folders") - .HasColumnType("TEXT"); - - b.Property("Key") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("Reprompt") - .HasColumnType("INTEGER"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("UserId"); - - b.ToTable("Cipher", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Folder", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CipherId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id") - .HasAnnotation("SqlServer:Clustered", true); - - b.HasIndex("CipherId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("SecurityTask", (string)null); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.Property("ProjectsId") - .HasColumnType("TEXT"); - - b.Property("SecretsId") - .HasColumnType("TEXT"); - - b.HasKey("ProjectsId", "SecretsId"); - - b.HasIndex("SecretsId"); - - b.ToTable("ProjectSecret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("GroupId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GroupId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("GroupId"); - - b.HasDiscriminator().HasValue("group_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("ServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("ServiceAccountId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("ServiceAccountId"); - - b.HasDiscriminator().HasValue("service_account_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedProjectId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedProjectId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedProjectId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_project"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedSecretId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedSecretId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedSecretId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); - - b.Property("GrantedServiceAccountId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("GrantedServiceAccountId"); - - b.Property("OrganizationUserId") - .ValueGeneratedOnUpdateSometimes() - .HasColumnType("TEXT") - .HasColumnName("OrganizationUserId"); - - b.HasIndex("GrantedServiceAccountId"); - - b.HasIndex("OrganizationUserId"); - - b.HasDiscriminator().HasValue("user_service_account"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.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.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") - .WithMany() - .HasForeignKey("OrganizationIntegrationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationIntegration"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Policies") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") - .WithMany() - .HasForeignKey("ResponseDeviceId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("ResponseDevice"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") - .WithMany() - .HasForeignKey("GranteeId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") - .WithMany() - .HasForeignKey("GrantorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Grantee"); - - b.Navigation("Grantor"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoConfigs") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("SsoUsers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("SsoUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") - .WithMany() - .HasForeignKey("InstallationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Installation"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Collections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany("CollectionCiphers") - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionCiphers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Collection"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionGroups") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") - .WithMany("CollectionUsers") - .HasForeignKey("CollectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("CollectionUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Collection"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Groups") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany("GroupUsers") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany("GroupUsers") - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Connections") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Domains") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") - .WithMany() - .HasForeignKey("SponsoredOrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") - .WithMany() - .HasForeignKey("SponsoringOrganizationId"); - - b.Navigation("SponsoredOrganization"); - - b.Navigation("SponsoringOrganization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("OrganizationUsers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("OrganizationUsers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Transactions") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Transactions") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Provider"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") - .WithMany() - .HasForeignKey("TaskId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("Task"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notification"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ApiKeys") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") - .WithMany() - .HasForeignKey("EditorOrganizationUserId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") - .WithMany() - .HasForeignKey("EditorServiceAccountId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") - .WithMany("SecretVersions") - .HasForeignKey("SecretId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EditorOrganizationUser"); - - b.Navigation("EditorServiceAccount"); - - b.Navigation("Secret"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany("Ciphers") - .HasForeignKey("OrganizationId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Ciphers") - .HasForeignKey("UserId"); - - b.Navigation("Organization"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") - .WithMany("Folders") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") - .WithMany() - .HasForeignKey("CipherId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") - .WithMany() - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Cipher"); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("ProjectSecret", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) - .WithMany() - .HasForeignKey("ProjectsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) - .WithMany() - .HasForeignKey("SecretsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedProject"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedSecret"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("GroupAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") - .WithMany() - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany("ProjectAccessPolicies") - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedProject"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("ServiceAccountAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") - .WithMany() - .HasForeignKey("ServiceAccountId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("ServiceAccount"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedProjectId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedProject"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedSecretId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedSecret"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") - .WithMany("UserAccessPolicies") - .HasForeignKey("GrantedServiceAccountId"); - - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId"); - - b.Navigation("GrantedServiceAccount"); - - b.Navigation("OrganizationUser"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Ciphers"); - - b.Navigation("Collections"); - - b.Navigation("Connections"); - - b.Navigation("Domains"); - - b.Navigation("Groups"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("Policies"); - - b.Navigation("SsoConfigs"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => - { - b.Navigation("CollectionCiphers"); - - b.Navigation("CollectionGroups"); - - b.Navigation("CollectionUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => - { - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => - { - b.Navigation("CollectionUsers"); - - b.Navigation("GroupUsers"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => - { - b.Navigation("Ciphers"); - - b.Navigation("Folders"); - - b.Navigation("OrganizationUsers"); - - b.Navigation("SsoUsers"); - - b.Navigation("Transactions"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => - { - b.Navigation("GroupAccessPolicies"); - - b.Navigation("SecretVersions"); - - b.Navigation("ServiceAccountAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("GroupAccessPolicies"); - - b.Navigation("ProjectAccessPolicies"); - - b.Navigation("UserAccessPolicies"); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => - { - b.Navigation("CollectionCiphers"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs b/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs deleted file mode 100644 index 36db0718fb3e..000000000000 --- a/util/SqliteMigrations/Migrations/20251231233301_DefaultCollectionSemaphore.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Bit.SqliteMigrations.Migrations; - -/// -public partial class DefaultCollectionSemaphore : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DefaultCollectionSemaphore", - columns: table => new - { - OrganizationUserId = table.Column(type: "TEXT", nullable: false), - CreationDate = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DefaultCollectionSemaphore", x => x.OrganizationUserId); - table.ForeignKey( - name: "FK_DefaultCollectionSemaphore_OrganizationUser_OrganizationUserId", - column: x => x.OrganizationUserId, - principalTable: "OrganizationUser", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DefaultCollectionSemaphore"); - } -} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 724261d026b7..fcf5ce834ec6 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -64,19 +64,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("OrganizationMemberBaseDetails"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.Property("OrganizationUserId") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.HasKey("OrganizationUserId"); - - b.ToTable("DefaultCollectionSemaphore", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -2606,17 +2593,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasDiscriminator().HasValue("user_service_account"); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.DefaultCollectionSemaphore", b => - { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") - .WithMany() - .HasForeignKey("OrganizationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationUser"); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") From dc5c06ff83fa9152cd56f657e6d3dd7d6abde4ac Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 14:16:47 +1000 Subject: [PATCH 35/41] Revert Bulk implementation --- .../Collections/CollectionUtils.cs | 3 +- .../Repositories/CollectionRepository.cs | 61 ++++++++++++++++++- .../Repositories/CollectionRepository.cs | 15 ++--- .../CreateDefaultCollectionsTests.cs | 7 --- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs index 6b2da70d3ea9..116992146f87 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs @@ -13,8 +13,7 @@ public static class CollectionUtils /// The IDs for organization users who need default collections. /// The encrypted string to use as the default collection name. /// A tuple containing the collections and collection users. - public static (IEnumerable collections, - IEnumerable collectionUsers) + public static (ICollection collections, ICollection collectionUsers) BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index a78a699b10ac..4b8cd3d37135 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.OrganizationFeatures.Collections; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -400,8 +401,64 @@ await connection.ExecuteAsync( public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { - // Use the stored procedure approach which handles filtering internally - await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); + organizationUserIds = organizationUserIds.ToList(); + if (!organizationUserIds.Any()) + { + return; + } + + await using var connection = new SqlConnection(ConnectionString); + connection.Open(); + await using var transaction = connection.BeginTransaction(); + try + { + var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(connection, transaction, organizationId); + + var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); + + var (collections, collectionUsers) = + CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); + + if (!collectionUsers.Any() || !collections.Any()) + { + return; + } + + await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); + await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + + private async Task> GetOrgUserIdsWithDefaultCollectionAsync(SqlConnection connection, SqlTransaction transaction, Guid organizationId) + { + const string sql = @" + SELECT + ou.Id AS OrganizationUserId + FROM + OrganizationUser ou + INNER JOIN + CollectionUser cu ON cu.OrganizationUserId = ou.Id + INNER JOIN + Collection c ON c.Id = cu.CollectionId + WHERE + ou.OrganizationId = @OrganizationId + AND c.Type = @CollectionType; + "; + + var organizationUserIds = await connection.QueryAsync( + sql, + new { OrganizationId = organizationId, CollectionType = CollectionType.DefaultUserCollection }, + transaction: transaction + ); + + return organizationUserIds.ToHashSet(); } public class CollectionWithGroupsAndUsers : Collection diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 6211f7187308..8146e7c53dfc 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -797,8 +797,8 @@ private static async Task ReplaceCollectionUsersAsync(DatabaseContext dbContext, public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { - organizationUserIds = organizationUserIds.ToList(); - if (!organizationUserIds.Any()) + var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); + if (organizationUserIdsHashSet.Count == 0) { return; } @@ -807,18 +807,15 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable var dbContext = GetDatabaseContext(scope); // Query for users who already have default collections - var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); - var existingOrgUserIds = await dbContext.CollectionUsers + var existingOrgUserIds = dbContext.CollectionUsers .Where(cu => organizationUserIdsHashSet.Contains(cu.OrganizationUserId)) .Where(cu => cu.Collection.Type == CollectionType.DefaultUserCollection) .Where(cu => cu.Collection.OrganizationId == organizationId) - .Select(cu => cu.OrganizationUserId) - .ToListAsync(); + .Select(cu => cu.OrganizationUserId); // Filter to only users who need collections - var filteredOrgUserIds = organizationUserIds.Except(existingOrgUserIds).ToList(); - - if (!filteredOrgUserIds.Any()) + var filteredOrgUserIds = organizationUserIdsHashSet.Except(existingOrgUserIds).ToList(); + if (filteredOrgUserIds.Count == 0) { return; } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index c778b24fd408..283760dbec61 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -6,10 +6,6 @@ namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.Collectio public class CreateDefaultCollectionsTests { - /// - /// Test that CreateDefaultCollectionsAsync successfully creates default collections for new users - /// with correct permissions - /// [Theory, DatabaseData] public async Task CreateDefaultCollectionsAsync_CreatesDefaultCollections_Success( IUserRepository userRepository, @@ -63,9 +59,6 @@ await collectionRepository.CreateDefaultCollectionsAsync( Assert.True(orgUser2CollectionUser.Manage); } - /// - /// Test that calling CreateDefaultCollectionsAsync multiple times does NOT create duplicates - /// [Theory, DatabaseData] public async Task CreateDefaultCollectionsAsync_CalledMultipleTimesForSameOrganizationUser_DoesNotCreateDuplicates( IUserRepository userRepository, From 1f4fc9b017fe7686c22064dad09917402f4509c6 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 14:19:09 +1000 Subject: [PATCH 36/41] dotnet format --- .../Repositories/CollectionRepository.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 8146e7c53dfc..d8071c35bd33 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -3,7 +3,6 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using LinqToDB.EntityFrameworkCore; From 4c32e20fd71d180e06211e8cd6f23b2f8a794523 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 14:25:24 +1000 Subject: [PATCH 37/41] Revert unnecessary ef changes --- .../Repositories/CollectionRepository.cs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index d8071c35bd33..197caa5f4665 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -3,6 +3,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; +using Bit.Core.Utilities; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using LinqToDB.EntityFrameworkCore; @@ -796,8 +797,8 @@ private static async Task ReplaceCollectionUsersAsync(DatabaseContext dbContext, public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) { - var organizationUserIdsHashSet = organizationUserIds.ToHashSet(); - if (organizationUserIdsHashSet.Count == 0) + organizationUserIds = organizationUserIds.ToList(); + if (!organizationUserIds.Any()) { return; } @@ -805,43 +806,46 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - // Query for users who already have default collections - var existingOrgUserIds = dbContext.CollectionUsers - .Where(cu => organizationUserIdsHashSet.Contains(cu.OrganizationUserId)) - .Where(cu => cu.Collection.Type == CollectionType.DefaultUserCollection) - .Where(cu => cu.Collection.OrganizationId == organizationId) - .Select(cu => cu.OrganizationUserId); + var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(dbContext, organizationId); + var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); - // Filter to only users who need collections - var filteredOrgUserIds = organizationUserIdsHashSet.Except(existingOrgUserIds).ToList(); - if (filteredOrgUserIds.Count == 0) + var (collectionUsers, collections) = CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); + + if (!collectionUsers.Any() || !collections.Any()) { return; } - var (collections, collectionUsers) = - CollectionUtils.BuildDefaultUserCollections(organizationId, filteredOrgUserIds, defaultCollectionName); - - await using var transaction = await dbContext.Database.BeginTransactionAsync(); - - try - { - await dbContext.BulkCopyAsync(Mapper.Map>(collections)); - await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); + await dbContext.BulkCopyAsync(collections); + await dbContext.BulkCopyAsync(collectionUsers); - await transaction.CommitAsync(); - } - catch - { - await transaction.RollbackAsync(); - throw; - } + await dbContext.SaveChangesAsync(); } - public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName) + private async Task> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) { - // EF uses the same bulk copy approach as the main method - await CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); + var results = await dbContext.OrganizationUsers + .Where(ou => ou.OrganizationId == organizationId) + .Join( + dbContext.CollectionUsers, + ou => ou.Id, + cu => cu.OrganizationUserId, + (ou, cu) => new { ou, cu } + ) + .Join( + dbContext.Collections, + temp => temp.cu.CollectionId, + c => c.Id, + (temp, c) => new { temp.ou, Collection = c } + ) + .Where(x => x.Collection.Type == CollectionType.DefaultUserCollection) + .Select(x => x.ou.Id) + .ToListAsync(); + + return results.ToHashSet(); } + public Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, + string defaultCollectionName) => + CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName); } From 32dd846b9e344820b87a0fd0eccfa0fd78196c06 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 14:26:30 +1000 Subject: [PATCH 38/41] Bump migration date --- ...sql => 2026-01-06_00_Collection_CreateDefaultCollections.sql} | 1 - 1 file changed, 1 deletion(-) rename util/Migrator/DbScripts/{2025-12-30_01_Collection_CreateDefaultCollections.sql => 2026-01-06_00_Collection_CreateDefaultCollections.sql} (96%) diff --git a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2026-01-06_00_Collection_CreateDefaultCollections.sql similarity index 96% rename from util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql rename to util/Migrator/DbScripts/2026-01-06_00_Collection_CreateDefaultCollections.sql index 7afee3e41259..c7935db5e846 100644 --- a/util/Migrator/DbScripts/2025-12-30_01_Collection_CreateDefaultCollections.sql +++ b/util/Migrator/DbScripts/2026-01-06_00_Collection_CreateDefaultCollections.sql @@ -1,6 +1,5 @@ -- Creates default user collections for organization users -- Filters out existing default collections at database level --- NOTE: this MUST be executed in a single transaction to ensure consistency CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections] @OrganizationId UNIQUEIDENTIFIER, @DefaultCollectionName VARCHAR(MAX), From 3d801be4e0588083522f5b50fc1e0ec0744a7c66 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 14:43:03 +1000 Subject: [PATCH 39/41] Revert unnecessary new tests --- ...zationDataOwnershipPolicyValidatorTests.cs | 68 ------------------- .../CreateDefaultCollectionsBulkTests.cs | 52 ++------------ 2 files changed, 6 insertions(+), 114 deletions(-) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs index d7415fbdab2c..dd2f1d76e802 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs @@ -181,74 +181,6 @@ await collectionRepository _defaultUserCollectionName); } - [Theory] - [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] - public async Task ExecuteSideEffectsAsync_PassesAllUsers_RepositoryFiltersInternally( - Policy postUpdatedPolicy, - Policy? previousPolicyState, - [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, - [OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable orgPolicyDetails, - OrganizationDataOwnershipPolicyRequirementFactory factory) - { - // Arrange - var orgPolicyDetailsList = orgPolicyDetails.ToList(); - foreach (var policyDetail in orgPolicyDetailsList) - { - policyDetail.OrganizationId = policyUpdate.OrganizationId; - } - - var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); - var collectionRepository = Substitute.For(); - - var sut = ArrangeSut(factory, policyRepository, collectionRepository); - var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); - - // Act - await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - - // Assert - Should pass all user IDs (repository does internal filtering) - await collectionRepository - .Received(1) - .CreateDefaultCollectionsBulkAsync( - policyUpdate.OrganizationId, - Arg.Is>(ids => ids.Count() == 3), - _defaultUserCollectionName); - } - - [Theory] - [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] - public async Task ExecuteSideEffectsAsync_CallsRepositoryWithAllUsers_EvenIfAllHaveCollections( - Policy postUpdatedPolicy, - Policy? previousPolicyState, - [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, - [OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable orgPolicyDetails, - OrganizationDataOwnershipPolicyRequirementFactory factory) - { - // Arrange - var orgPolicyDetailsList = orgPolicyDetails.ToList(); - foreach (var policyDetail in orgPolicyDetailsList) - { - policyDetail.OrganizationId = policyUpdate.OrganizationId; - } - - var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); - var collectionRepository = Substitute.For(); - - var sut = ArrangeSut(factory, policyRepository, collectionRepository); - var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); - - // Act - await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); - - // Assert - Should call repository with all user IDs (repository filters internally) - await collectionRepository - .Received(1) - .CreateDefaultCollectionsBulkAsync( - policyUpdate.OrganizationId, - Arg.Is>(ids => ids.Count() == 3), - _defaultUserCollectionName); - } - private static IEnumerable WhenDefaultCollectionsDoesNotExistTestCases() { yield return [new OrganizationModelOwnershipPolicyModel(null)]; diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs index a1ac02e1ec18..bcc941a27ec1 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs @@ -1,5 +1,4 @@ - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; @@ -37,7 +36,7 @@ public async Task CreateDefaultCollectionsBulkAsync_ShouldCreateDefaultCollectio } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AutoFiltersExisting( + public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -61,20 +60,20 @@ public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AutoF await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) }; - var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers); + var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers).ToList(); var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); - // Act - Pass all user IDs, method should auto-filter existing users + // Act await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); - // Assert - All users now have exactly one collection + // Assert await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id); await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_DoesNotCreateDuplicates_WhenUsersAlreadyHaveOne( + public async Task CreateDefaultCollectionsBulkAsync_IgnoresAllExistingUsers( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, @@ -102,45 +101,6 @@ public async Task CreateDefaultCollectionsBulkAsync_DoesNotCreateDuplicates_When await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); } - [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_AutoFilters_WhenMixedUsersProvided( - IOrganizationRepository organizationRepository, - IUserRepository userRepository, - IOrganizationUserRepository organizationUserRepository, - ICollectionRepository collectionRepository) - { - // Arrange - var organization = await organizationRepository.CreateTestOrganizationAsync(); - - var existingUser = await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization); - var newUser = await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization); - var defaultCollectionName = $"default-name-{organization.Id}"; - - // Create collection for existing user - await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, [existingUser.Id], defaultCollectionName); - - // Act - Pass both users, method should auto-filter and only create for new user - await collectionRepository.CreateDefaultCollectionsBulkAsync( - organization.Id, - [existingUser.Id, newUser.Id], - defaultCollectionName); - - // Assert - Verify existing user still has exactly one collection - var existingUserCollections = await collectionRepository.GetManyByUserIdAsync(existingUser.UserId!.Value); - var existingUserDefaultCollections = existingUserCollections - .Where(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection) - .ToList(); - Assert.Single(existingUserDefaultCollections); - - // Verify new user now has collection (was created) - var newUserCollections = await collectionRepository.GetManyByUserIdAsync(newUser.UserId!.Value); - var newUserDefaultCollection = newUserCollections - .SingleOrDefault(c => c.OrganizationId == organization.Id && c.Type == CollectionType.DefaultUserCollection); - Assert.NotNull(newUserDefaultCollection); - - await CleanupAsync(organizationRepository, userRepository, organization, [existingUser, newUser]); - } - private static async Task CreateUsersWithExistingDefaultCollectionsAsync(ICollectionRepository collectionRepository, Guid organizationId, IEnumerable affectedOrgUserIds, string defaultCollectionName, OrganizationUser[] resultOrganizationUsers) From 9d86affb7c218d92a6e86ae08700fb67e0177bc6 Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 15:00:16 +1000 Subject: [PATCH 40/41] DRY up tests --- .../Repositories/CollectionRepository.cs | 9 +- .../CreateDefaultCollectionsBulkTests.cs | 141 +++------------- .../CreateDefaultCollectionsSharedTests.cs | 158 ++++++++++++++++++ .../CreateDefaultCollectionsTests.cs | 117 ++++--------- 4 files changed, 219 insertions(+), 206 deletions(-) create mode 100644 test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index 197caa5f4665..49b869033d52 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -3,7 +3,6 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; -using Bit.Core.Utilities; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using LinqToDB.EntityFrameworkCore; @@ -809,15 +808,15 @@ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(dbContext, organizationId); var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection); - var (collectionUsers, collections) = CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); + var (collections, collectionUsers) = CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName); - if (!collectionUsers.Any() || !collections.Any()) + if (!collections.Any() || !collectionUsers.Any()) { return; } - await dbContext.BulkCopyAsync(collections); - await dbContext.BulkCopyAsync(collectionUsers); + await dbContext.BulkCopyAsync(Mapper.Map>(collections)); + await dbContext.BulkCopyAsync(Mapper.Map>(collectionUsers)); await dbContext.SaveChangesAsync(); } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs index bcc941a27ec1..712ad7d62e38 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs @@ -1,38 +1,24 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Entities; -using Bit.Core.Enums; -using Bit.Core.Repositories; +using Bit.Core.Repositories; using Xunit; namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; -public class CreateDefaultCollectionsBulkTests + +public class CreateDefaultCollectionsBulkAsyncTests { [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection( + public async Task CreateDefaultCollectionsBulkAsync_CreatesDefaultCollections_Success( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { - // Arrange - var organization = await organizationRepository.CreateTestOrganizationAsync(); - - var resultOrganizationUsers = await Task.WhenAll( - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) - ); - - var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); - var defaultCollectionName = $"default-name-{organization.Id}"; - - // Act - await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); - - // Assert - await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - - await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); + await CreateDefaultCollectionsSharedTests.CreatesDefaultCollections_Success( + collectionRepository.CreateDefaultCollectionsBulkAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); } [Theory, DatabaseData] @@ -42,34 +28,12 @@ public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AndIg IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { - // Arrange - var organization = await organizationRepository.CreateTestOrganizationAsync(); - - var arrangedOrganizationUsers = await Task.WhenAll( - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) - ); - - var arrangedOrgUserIds = arrangedOrganizationUsers.Select(organizationUser => organizationUser.Id); - var defaultCollectionName = $"default-name-{organization.Id}"; - - await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, arrangedOrgUserIds, defaultCollectionName, arrangedOrganizationUsers); - - var newOrganizationUsers = new List - { - await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) - }; - - var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers).ToList(); - var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); - - // Act - await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); - - // Assert - await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id); - - await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); + await CreateDefaultCollectionsSharedTests.CreatesForNewUsersOnly_AndIgnoresExistingUsers( + collectionRepository.CreateDefaultCollectionsBulkAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); } [Theory, DatabaseData] @@ -79,72 +43,11 @@ public async Task CreateDefaultCollectionsBulkAsync_IgnoresAllExistingUsers( IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { - // Arrange - var organization = await organizationRepository.CreateTestOrganizationAsync(); - - var resultOrganizationUsers = await Task.WhenAll( - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), - CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) - ); - - var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); - var defaultCollectionName = $"default-name-{organization.Id}"; - - await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); - - // Act - Try to create again, should silently filter and not create duplicates - await collectionRepository.CreateDefaultCollectionsBulkAsync(organization.Id, affectedOrgUserIds, defaultCollectionName); - - // Assert - Original collections should remain unchanged, still only one per user - await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); - - await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); - } - - private static async Task CreateUsersWithExistingDefaultCollectionsAsync(ICollectionRepository collectionRepository, - Guid organizationId, IEnumerable affectedOrgUserIds, string defaultCollectionName, - OrganizationUser[] resultOrganizationUsers) - { - await collectionRepository.CreateDefaultCollectionsBulkAsync(organizationId, affectedOrgUserIds, defaultCollectionName); - - await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organizationId); - } - - private static async Task AssertAllUsersHaveOneDefaultCollectionAsync(ICollectionRepository collectionRepository, - IEnumerable organizationUsers, Guid organizationId) - { - foreach (var organizationUser in organizationUsers) - { - var collectionDetails = await collectionRepository.GetManyByUserIdAsync(organizationUser!.UserId.Value); - var defaultCollection = collectionDetails - .SingleOrDefault(collectionDetail => - collectionDetail.OrganizationId == organizationId - && collectionDetail.Type == CollectionType.DefaultUserCollection); - - Assert.NotNull(defaultCollection); - } - } - - private static async Task CreateUserForOrgAsync(IUserRepository userRepository, - IOrganizationUserRepository organizationUserRepository, Organization organization) - { - var user = await userRepository.CreateTestUserAsync(); - var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - - return orgUser; - } - - private static async Task CleanupAsync(IOrganizationRepository organizationRepository, - IUserRepository userRepository, - Organization organization, - IEnumerable organizationUsers) - { - await organizationRepository.DeleteAsync(organization); - - await userRepository.DeleteManyAsync( - organizationUsers - .Where(organizationUser => organizationUser.UserId != null) - .Select(organizationUser => new User() { Id = organizationUser.UserId.Value }) - ); + await CreateDefaultCollectionsSharedTests.IgnoresAllExistingUsers( + collectionRepository.CreateDefaultCollectionsBulkAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); } } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs new file mode 100644 index 000000000000..0fb4a5b446e1 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs @@ -0,0 +1,158 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; + +/// +/// Shared tests for CreateDefaultCollections methods - both bulk and non-bulk implementations, +/// as they share the same behavior. Both test suites call the tests in this class. +/// +public static class CreateDefaultCollectionsSharedTests +{ + public static async Task CreatesDefaultCollections_Success( + Func, string, Task> createDefaultCollectionsFunc, + IOrganizationRepository organizationRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + + var resultOrganizationUsers = await Task.WhenAll( + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) + ); + + var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); + var defaultCollectionName = $"default-name-{organization.Id}"; + + // Act + await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName); + + // Assert + await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); + + await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); + } + + public static async Task CreatesForNewUsersOnly_AndIgnoresExistingUsers( + Func, string, Task> createDefaultCollectionsFunc, + IOrganizationRepository organizationRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + + var arrangedOrganizationUsers = await Task.WhenAll( + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) + ); + + var arrangedOrgUserIds = arrangedOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); + var defaultCollectionName = $"default-name-{organization.Id}"; + + await CreateUsersWithExistingDefaultCollectionsAsync(createDefaultCollectionsFunc, collectionRepository, organization.Id, arrangedOrgUserIds, defaultCollectionName, arrangedOrganizationUsers); + + var newOrganizationUsers = new List + { + await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) + }; + + var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers).ToList(); + var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList(); + + // Act + await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName); + + // Assert + await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id); + + await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers); + } + + public static async Task IgnoresAllExistingUsers( + Func, string, Task> createDefaultCollectionsFunc, + IOrganizationRepository organizationRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + + var resultOrganizationUsers = await Task.WhenAll( + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization), + CreateUserForOrgAsync(userRepository, organizationUserRepository, organization) + ); + + var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList(); + var defaultCollectionName = $"default-name-{organization.Id}"; + + await CreateUsersWithExistingDefaultCollectionsAsync(createDefaultCollectionsFunc, collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers); + + // Act - Try to create again, should silently filter and not create duplicates + await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName); + + // Assert - Original collections should remain unchanged, still only one per user + await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id); + + await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers); + } + + private static async Task CreateUsersWithExistingDefaultCollectionsAsync( + Func, string, Task> createDefaultCollectionsFunc, + ICollectionRepository collectionRepository, + Guid organizationId, + IEnumerable affectedOrgUserIds, + string defaultCollectionName, + OrganizationUser[] resultOrganizationUsers) + { + await createDefaultCollectionsFunc(organizationId, affectedOrgUserIds, defaultCollectionName); + + await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organizationId); + } + + private static async Task AssertAllUsersHaveOneDefaultCollectionAsync(ICollectionRepository collectionRepository, + IEnumerable organizationUsers, Guid organizationId) + { + foreach (var organizationUser in organizationUsers) + { + var collectionDetails = await collectionRepository.GetManyByUserIdAsync(organizationUser!.UserId.Value); + var defaultCollection = collectionDetails + .SingleOrDefault(collectionDetail => + collectionDetail.OrganizationId == organizationId + && collectionDetail.Type == CollectionType.DefaultUserCollection); + + Assert.NotNull(defaultCollection); + } + } + + private static async Task CreateUserForOrgAsync(IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, Organization organization) + { + var user = await userRepository.CreateTestUserAsync(); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); + + return orgUser; + } + + private static async Task CleanupAsync(IOrganizationRepository organizationRepository, + IUserRepository userRepository, + Organization organization, + IEnumerable organizationUsers) + { + await organizationRepository.DeleteAsync(organization); + + await userRepository.DeleteManyAsync( + organizationUsers + .Where(organizationUser => organizationUser.UserId != null) + .Select(organizationUser => new User() { Id = organizationUser.UserId.Value }) + ); + } +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 283760dbec61..0e3998d86dc8 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -1,99 +1,52 @@ -using Bit.Core.Enums; -using Bit.Core.Repositories; +using Bit.Core.Repositories; using Xunit; namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository; -public class CreateDefaultCollectionsTests +public class CreateDefaultCollectionsAsyncTests { [Theory, DatabaseData] public async Task CreateDefaultCollectionsAsync_CreatesDefaultCollections_Success( - IUserRepository userRepository, IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository) + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) { - // Arrange - var user1 = await userRepository.CreateTestUserAsync("user1"); - var user2 = await userRepository.CreateTestUserAsync("user2"); - var organization = await organizationRepository.CreateTestOrganizationAsync(); - var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user1); - var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user2); - - // Act - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser1.Id, orgUser2.Id], - "My Items"); - - // Assert - var collectionsWithAccess = await collectionRepository.GetManyByOrganizationIdWithAccessAsync(organization.Id); - var defaultCollections = collectionsWithAccess - .Where(c => c.Item1.Type == CollectionType.DefaultUserCollection) - .ToList(); - - Assert.Equal(2, defaultCollections.Count); - Assert.All(defaultCollections, c => Assert.Equal("My Items", c.Item1.Name)); - Assert.All(defaultCollections, c => Assert.Equal(organization.Id, c.Item1.OrganizationId)); - - // Verify each user has exactly 1 collection with correct permissions - var orgUser1Collection = Assert.Single(defaultCollections, - c => c.Item2.Users.FirstOrDefault()?.Id == orgUser1.Id); - - Assert.Empty(orgUser1Collection.Item2.Groups); - - var orgUser1CollectionUser = orgUser1Collection.Item2.Users.Single(); - Assert.False(orgUser1CollectionUser.ReadOnly); - Assert.False(orgUser1CollectionUser.HidePasswords); - Assert.True(orgUser1CollectionUser.Manage); - - // Verify each user has exactly 1 collection with correct permissions - var orgUser2Collection = Assert.Single(defaultCollections, - c => c.Item2.Users.FirstOrDefault()?.Id == orgUser2.Id); - - Assert.Empty(orgUser2Collection.Item2.Groups); - - var orgUser2CollectionUser = orgUser2Collection.Item2.Users.Single(); - Assert.False(orgUser2CollectionUser.ReadOnly); - Assert.False(orgUser2CollectionUser.HidePasswords); - Assert.True(orgUser2CollectionUser.Manage); + await CreateDefaultCollectionsSharedTests.CreatesDefaultCollections_Success( + collectionRepository.CreateDefaultCollectionsAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsAsync_CalledMultipleTimesForSameOrganizationUser_DoesNotCreateDuplicates( - IUserRepository userRepository, + public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers( IOrganizationRepository organizationRepository, - ICollectionRepository collectionRepository, - IOrganizationUserRepository organizationUserRepository) + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) { - // Arrange - var user = await userRepository.CreateTestUserAsync(); - var organization = await organizationRepository.CreateTestOrganizationAsync(); - var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user); - - // Act - Call twice - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser.Id], - "My Items"); - - // Second call should silently filter and not create duplicate - await collectionRepository.CreateDefaultCollectionsAsync( - organization.Id, - [orgUser.Id], - "My Items Duplicate"); - - // Assert - Only one collection should exist - var collections = await collectionRepository.GetManyByOrganizationIdAsync(organization.Id); - var defaultCollections = collections.Where(c => c.Type == CollectionType.DefaultUserCollection).ToList(); - - Assert.Single(defaultCollections); + await CreateDefaultCollectionsSharedTests.CreatesForNewUsersOnly_AndIgnoresExistingUsers( + collectionRepository.CreateDefaultCollectionsBulkAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); + } - var access = await collectionRepository.GetManyUsersByIdAsync(defaultCollections.Single().Id); - var userAccess = Assert.Single(access); - Assert.Equal(orgUser.Id, userAccess.Id); - Assert.False(userAccess.ReadOnly); - Assert.False(userAccess.HidePasswords); - Assert.True(userAccess.Manage); + [Theory, DatabaseData] + public async Task CreateDefaultCollectionsAsync_IgnoresAllExistingUsers( + IOrganizationRepository organizationRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) + { + await CreateDefaultCollectionsSharedTests.IgnoresAllExistingUsers( + collectionRepository.CreateDefaultCollectionsAsync, + organizationRepository, + userRepository, + organizationUserRepository, + collectionRepository); } } From 88036ea88c6aaa490206f9731808111b066c8ded Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Tue, 6 Jan 2026 15:11:35 +1000 Subject: [PATCH 41/41] local review feedback --- .../Repositories/CollectionRepository.cs | 4 +-- .../Repositories/DatabaseExceptionHelpers.cs | 17 ---------- .../Repositories/DatabaseExceptionHelpers.cs | 34 ------------------- .../CreateDefaultCollectionsTests.cs | 4 +-- 4 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs delete mode 100644 src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 4b8cd3d37135..153170342729 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -427,11 +427,11 @@ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumer await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections); await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers); - transaction.Commit(); + await transaction.CommitAsync(); } catch { - transaction.Rollback(); + await transaction.RollbackAsync(); throw; } } diff --git a/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs b/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs deleted file mode 100644 index 4403fb4bfc2b..000000000000 --- a/src/Infrastructure.Dapper/Repositories/DatabaseExceptionHelpers.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Bit.Infrastructure.Dapper.Repositories; - -#nullable enable - -internal static class DatabaseExceptionHelpers -{ - /// - /// Determines if an exception represents a SQL Server duplicate key constraint violation. - /// - public static bool IsDuplicateKeyException(Exception exception) - { - ArgumentNullException.ThrowIfNull(exception); - - return exception is Microsoft.Data.SqlClient.SqlException { Errors: not null } msEx && - msEx.Errors.Cast().Any(error => error.Number == 2627); - } -} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs deleted file mode 100644 index 7d297efcc9f6..000000000000 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseExceptionHelpers.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Bit.Infrastructure.EntityFramework.Repositories; - -#nullable enable - -internal static class DatabaseExceptionHelpers -{ - /// - /// Determines if a DbUpdateException represents a duplicate key constraint violation. - /// Works with MySQL, SQL Server, PostgreSQL, and SQLite. - /// - public static bool IsDuplicateKeyException(Exception exception) - { - ArgumentNullException.ThrowIfNull(exception); - - switch (exception) - { - // MySQL - case MySqlConnector.MySqlException myEx: - return myEx.ErrorCode == MySqlConnector.MySqlErrorCode.DuplicateKeyEntry; - // SQL Server - case Microsoft.Data.SqlClient.SqlException msEx: - return msEx.Errors != null && - msEx.Errors.Cast().Any(error => error.Number == 2627); - // PostgreSQL - case Npgsql.PostgresException pgEx: - return pgEx.SqlState == "23505"; - // SQLite - case Microsoft.Data.Sqlite.SqliteException liteEx: - return liteEx is { SqliteErrorCode: 19, SqliteExtendedErrorCode: 1555 }; - default: - return false; - } - } -} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs index 0e3998d86dc8..bd894e9ca59a 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs @@ -21,14 +21,14 @@ await CreateDefaultCollectionsSharedTests.CreatesDefaultCollections_Success( } [Theory, DatabaseData] - public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers( + public async Task CreateDefaultCollectionsAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers( IOrganizationRepository organizationRepository, IUserRepository userRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { await CreateDefaultCollectionsSharedTests.CreatesForNewUsersOnly_AndIgnoresExistingUsers( - collectionRepository.CreateDefaultCollectionsBulkAsync, + collectionRepository.CreateDefaultCollectionsAsync, organizationRepository, userRepository, organizationUserRepository,