From 6800bc57f3eb492222e128cffcd00e16b29cc155 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Tue, 17 Jun 2025 13:30:56 -0400 Subject: [PATCH 1/8] [PM-18555] Main part of notifications refactor (#5757) * More tests * More tests * Add non-guid tests * Introduce slimmer services * Implement IPushEngine on services * Implement IPushEngine * Fix tests * Format * Switch to `Guid` on `PushSendRequestModel` * Remove TODOs --- .../Push/Controllers/PushController.cs | 54 +- .../Api/Request/PushSendRequestModel.cs | 18 +- .../NotificationHubPushNotificationService.cs | 408 +++------------ .../AzureQueuePushNotificationService.cs | 226 +------- .../Platform/Push/Services/IPushEngine.cs | 13 + .../Push/Services/IPushNotificationService.cs | 425 ++++++++++++++- .../Platform/Push/Services/IPushRelayer.cs | 44 ++ .../MultiServicePushNotificationService.cs | 215 ++------ .../Services/NoopPushNotificationService.cs | 123 +---- ...NotificationsApiPushNotificationService.cs | 237 +-------- .../Push/Services/PushNotification.cs | 78 +++ .../Services/RelayPushNotificationService.cs | 353 ++----------- .../Utilities/ServiceCollectionExtensions.cs | 11 +- .../Factories/ApiApplicationFactory.cs | 2 + .../Controllers/PushControllerTests.cs | 449 ++++++++++++++++ .../Push/Controllers/PushControllerTests.cs | 204 -------- .../Api/Request/PushSendRequestModelTests.cs | 74 ++- ...ficationHubPushNotificationServiceTests.cs | 487 +----------------- .../AzureQueuePushNotificationServiceTests.cs | 99 +--- ...ultiServicePushNotificationServiceTests.cs | 92 +--- ...icationsApiPushNotificationServiceTests.cs | 11 +- .../Platform/Push/Services/PushTestBase.cs | 21 +- .../RelayPushNotificationServiceTests.cs | 35 +- 23 files changed, 1271 insertions(+), 2408 deletions(-) create mode 100644 src/Core/Platform/Push/Services/IPushEngine.cs create mode 100644 src/Core/Platform/Push/Services/IPushRelayer.cs create mode 100644 src/Core/Platform/Push/Services/PushNotification.cs create mode 100644 test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs diff --git a/src/Api/Platform/Push/Controllers/PushController.cs b/src/Api/Platform/Push/Controllers/PushController.cs index 2a1f2b987d..af24a7b2ca 100644 --- a/src/Api/Platform/Push/Controllers/PushController.cs +++ b/src/Api/Platform/Push/Controllers/PushController.cs @@ -1,8 +1,11 @@ -using Bit.Core.Context; +using System.Diagnostics; +using System.Text.Json; +using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Api; using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -20,14 +23,14 @@ namespace Bit.Api.Platform.Push; public class PushController : Controller { private readonly IPushRegistrationService _pushRegistrationService; - private readonly IPushNotificationService _pushNotificationService; + private readonly IPushRelayer _pushRelayer; private readonly IWebHostEnvironment _environment; private readonly ICurrentContext _currentContext; private readonly IGlobalSettings _globalSettings; public PushController( IPushRegistrationService pushRegistrationService, - IPushNotificationService pushNotificationService, + IPushRelayer pushRelayer, IWebHostEnvironment environment, ICurrentContext currentContext, IGlobalSettings globalSettings) @@ -35,7 +38,7 @@ public class PushController : Controller _currentContext = currentContext; _environment = environment; _pushRegistrationService = pushRegistrationService; - _pushNotificationService = pushNotificationService; + _pushRelayer = pushRelayer; _globalSettings = globalSettings; } @@ -74,31 +77,50 @@ public class PushController : Controller } [HttpPost("send")] - public async Task SendAsync([FromBody] PushSendRequestModel model) + public async Task SendAsync([FromBody] PushSendRequestModel model) { CheckUsage(); - if (!string.IsNullOrWhiteSpace(model.InstallationId)) + NotificationTarget target; + Guid targetId; + + if (model.InstallationId.HasValue) { - if (_currentContext.InstallationId!.Value.ToString() != model.InstallationId!) + if (_currentContext.InstallationId!.Value != model.InstallationId.Value) { throw new BadRequestException("InstallationId does not match current context."); } - await _pushNotificationService.SendPayloadToInstallationAsync( - _currentContext.InstallationId.Value.ToString(), model.Type, model.Payload, Prefix(model.Identifier), - Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.Installation; + targetId = _currentContext.InstallationId.Value; } - else if (!string.IsNullOrWhiteSpace(model.UserId)) + else if (model.UserId.HasValue) { - await _pushNotificationService.SendPayloadToUserAsync(Prefix(model.UserId), - model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.User; + targetId = model.UserId.Value; } - else if (!string.IsNullOrWhiteSpace(model.OrganizationId)) + else if (model.OrganizationId.HasValue) { - await _pushNotificationService.SendPayloadToOrganizationAsync(Prefix(model.OrganizationId), - model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.Organization; + targetId = model.OrganizationId.Value; } + else + { + throw new UnreachableException("Model validation should have prevented getting here."); + } + + var notification = new RelayedNotification + { + Type = model.Type, + Target = target, + TargetId = targetId, + Payload = model.Payload, + Identifier = model.Identifier, + DeviceId = model.DeviceId, + ClientType = model.ClientType, + }; + + await _pushRelayer.RelayAsync(_currentContext.InstallationId.Value, notification); } private string Prefix(string value) diff --git a/src/Core/Models/Api/Request/PushSendRequestModel.cs b/src/Core/Models/Api/Request/PushSendRequestModel.cs index 0ef7e999e3..19f89d931f 100644 --- a/src/Core/Models/Api/Request/PushSendRequestModel.cs +++ b/src/Core/Models/Api/Request/PushSendRequestModel.cs @@ -4,22 +4,22 @@ using Bit.Core.Enums; namespace Bit.Core.Models.Api; -public class PushSendRequestModel : IValidatableObject +public class PushSendRequestModel : IValidatableObject { - public string? UserId { get; set; } - public string? OrganizationId { get; set; } - public string? DeviceId { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public Guid? DeviceId { get; set; } public string? Identifier { get; set; } public required PushType Type { get; set; } - public required object Payload { get; set; } + public required T Payload { get; set; } public ClientType? ClientType { get; set; } - public string? InstallationId { get; set; } + public Guid? InstallationId { get; set; } public IEnumerable Validate(ValidationContext validationContext) { - if (string.IsNullOrWhiteSpace(UserId) && - string.IsNullOrWhiteSpace(OrganizationId) && - string.IsNullOrWhiteSpace(InstallationId)) + if (!UserId.HasValue && + !OrganizationId.HasValue && + !InstallationId.HasValue) { yield return new ValidationResult( $"{nameof(UserId)} or {nameof(OrganizationId)} or {nameof(InstallationId)} is required."); diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs index 368c0f731b..81ec82a25d 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs @@ -1,21 +1,17 @@ #nullable enable using System.Text.Json; using System.Text.RegularExpressions; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using Notification = Bit.Core.NotificationCenter.Entities.Notification; namespace Bit.Core.NotificationHub; @@ -26,52 +22,32 @@ namespace Bit.Core.NotificationHub; /// Used by Cloud-Hosted environments. /// Received by Firebase for Android or APNS for iOS. /// -public class NotificationHubPushNotificationService : IPushNotificationService +public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer { private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly IHttpContextAccessor _httpContextAccessor; private readonly bool _enableTracing = false; private readonly INotificationHubPool _notificationHubPool; private readonly ILogger _logger; - private readonly IGlobalSettings _globalSettings; - private readonly TimeProvider _timeProvider; public NotificationHubPushNotificationService( IInstallationDeviceRepository installationDeviceRepository, INotificationHubPool notificationHubPool, IHttpContextAccessor httpContextAccessor, ILogger logger, - IGlobalSettings globalSettings, - TimeProvider timeProvider) + IGlobalSettings globalSettings) { _installationDeviceRepository = installationDeviceRepository; _httpContextAccessor = httpContextAccessor; _notificationHubPool = notificationHubPool; _logger = logger; - _globalSettings = globalSettings; - _timeProvider = timeProvider; if (globalSettings.Installation.Id == Guid.Empty) { logger.LogWarning("Installation ID is not set. Push notifications for installations will not work."); } } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -93,311 +69,17 @@ public class NotificationHubPushNotificationService : IPushNotificationService CollectionIds = collectionIds, }; - await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true); - } - } - - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendPayloadToUserAsync(folder.UserId, type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification + await PushAsync(new PushNotification { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendPayloadToUserAsync(message.UserId, type, message, true); + Type = type, + Target = NotificationTarget.User, + TargetId = cipher.UserId.Value, + Payload = message, + ExcludeCurrentContext = true, + }); } } - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - public async Task PushNotificationAsync(Notification notification) - { - Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty - ? _globalSettings.Installation.Id - : null; - - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = installationId, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - if (notification.Global) - { - if (installationId.HasValue) - { - await SendPayloadToInstallationAsync(installationId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else - { - _logger.LogWarning( - "Invalid global notification id {NotificationId} push notification. No installation id provided.", - notification.Id); - } - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); - } - } - - public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty - ? _globalSettings.Installation.Id - : null; - - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = installationId, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - if (notification.Global) - { - if (installationId.HasValue) - { - await SendPayloadToInstallationAsync(installationId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else - { - _logger.LogWarning( - "Invalid global notification status id {NotificationId} push notification. No installation id provided.", - notification.Id); - } - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus, - message, true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); - } - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendPayloadToUserAsync(authRequest.UserId, type, message, true); - } - - private async Task SendPayloadToInstallationAsync(Guid installationId, PushType type, object payload, - bool excludeCurrentContext, ClientType? clientType = null) - { - await SendPayloadToInstallationAsync(installationId.ToString(), type, payload, - GetContextIdentifier(excludeCurrentContext), clientType: clientType); - } - - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, - ClientType? clientType = null) - { - await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext), - clientType: clientType); - } - - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, - bool excludeCurrentContext, ClientType? clientType = null) - { - await SendPayloadToOrganizationAsync(orgId.ToString(), type, payload, - GetContextIdentifier(excludeCurrentContext), clientType: clientType); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - public async Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, - string? identifier, string? deviceId = null, ClientType? clientType = null) - { - var tag = BuildTag($"template:payload && installationId:{installationId}", identifier, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendPayloadToOrganizationAsync( - organization.Id, - PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, - false - ); - private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) @@ -425,13 +107,73 @@ public class NotificationHubPushNotificationService : IPushNotificationService return $"({tag})"; } - private async Task SendPayloadAsync(string tag, PushType type, object payload) + public async Task PushAsync(PushNotification pushNotification) + where T : class { + var initialTag = pushNotification.Target switch + { + NotificationTarget.User => $"template:payload_userId:{pushNotification.TargetId}", + NotificationTarget.Organization => $"template:payload && organizationId:{pushNotification.TargetId}", + NotificationTarget.Installation => $"template:payload && installationId:{pushNotification.TargetId}", + _ => throw new InvalidOperationException($"Push notification target '{pushNotification.Target}' is not valid."), + }; + + await PushCoreAsync( + initialTag, + GetContextIdentifier(pushNotification.ExcludeCurrentContext), + pushNotification.Type, + pushNotification.ClientType, + pushNotification.Payload + ); + } + + public async Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification) + { + // Relayed notifications need identifiers prefixed with the installation they are from and a underscore + var initialTag = relayedNotification.Target switch + { + NotificationTarget.User => $"template:payload_userId:{fromInstallation}_{relayedNotification.TargetId}", + NotificationTarget.Organization => $"template:payload && organizationId:{fromInstallation}_{relayedNotification.TargetId}", + NotificationTarget.Installation => $"template:payload && installationId:{fromInstallation}", + _ => throw new InvalidOperationException($"Invalid Notification target {relayedNotification.Target}"), + }; + + await PushCoreAsync( + initialTag, + relayedNotification.Identifier, + relayedNotification.Type, + relayedNotification.ClientType, + relayedNotification.Payload + ); + + if (relayedNotification.DeviceId.HasValue) + { + await _installationDeviceRepository.UpsertAsync( + new InstallationDeviceEntity(fromInstallation, relayedNotification.DeviceId.Value) + ); + } + else + { + _logger.LogWarning( + "A related notification of type '{Type}' came through without a device id from installation {Installation}", + relayedNotification.Type, + fromInstallation + ); + } + } + + private async Task PushCoreAsync(string initialTag, string? contextId, PushType pushType, ClientType? clientType, T payload) + { + var finalTag = BuildTag(initialTag, contextId, clientType); + var results = await _notificationHubPool.AllClients.SendTemplateNotificationAsync( new Dictionary { - { "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) } - }, tag); + { "type", ((byte)pushType).ToString() }, + { "payload", JsonSerializer.Serialize(payload) }, + }, + finalTag + ); if (_enableTracing) { @@ -444,7 +186,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService _logger.LogInformation( "Azure Notification Hub Tracking ID: {Id} | {Type} push notification with {Success} successes and {Failure} failures with a payload of {@Payload} and result of {@Results}", - outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results); + outcome.TrackingId, pushType, outcome.Success, outcome.Failure, payload, outcome.Results); } } } diff --git a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs index 05d1dd2d1d..94a20f1971 100644 --- a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs @@ -1,14 +1,10 @@ #nullable enable using System.Text.Json; using Azure.Storage.Queues; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; @@ -17,12 +13,10 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; -public class AzureQueuePushNotificationService : IPushNotificationService +public class AzureQueuePushNotificationService : IPushEngine { private readonly QueueClient _queueClient; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IGlobalSettings _globalSettings; - private readonly TimeProvider _timeProvider; public AzureQueuePushNotificationService( [FromKeyedServices("notifications")] QueueClient queueClient, @@ -33,30 +27,13 @@ public class AzureQueuePushNotificationService : IPushNotificationService { _queueClient = queueClient; _httpContextAccessor = httpContextAccessor; - _globalSettings = globalSettings; - _timeProvider = timeProvider; if (globalSettings.Installation.Id == Guid.Empty) { logger.LogWarning("Installation ID is not set. Push notifications for installations will not work."); } } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -83,166 +60,6 @@ public class AzureQueuePushNotificationService : IPushNotificationService } } - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendMessageAsync(type, message, excludeCurrentContext); - } - - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - public async Task PushNotificationAsync(Notification notification) - { - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - await SendMessageAsync(PushType.Notification, message, true); - } - - public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - await SendMessageAsync(PushType.NotificationStatus, message, true); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification - { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - } - private async Task SendMessageAsync(PushType type, T payload, bool excludeCurrentContext) { var contextId = GetContextIdentifier(excludeCurrentContext); @@ -263,42 +80,9 @@ public class AzureQueuePushNotificationService : IPushNotificationService return currentContext?.DeviceIdentifier; } - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - // Noop - Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) + public async Task PushAsync(PushNotification pushNotification) + where T : class { - // Noop - return Task.FromResult(0); + await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext); } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - // Noop - return Task.FromResult(0); - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, false); } diff --git a/src/Core/Platform/Push/Services/IPushEngine.cs b/src/Core/Platform/Push/Services/IPushEngine.cs new file mode 100644 index 0000000000..bde4ddaf4b --- /dev/null +++ b/src/Core/Platform/Push/Services/IPushEngine.cs @@ -0,0 +1,13 @@ +#nullable enable +using Bit.Core.Enums; +using Bit.Core.Vault.Entities; + +namespace Bit.Core.Platform.Push; + +public interface IPushEngine +{ + Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds); + + Task PushAsync(PushNotification pushNotification) + where T : class; +} diff --git a/src/Core/Platform/Push/Services/IPushNotificationService.cs b/src/Core/Platform/Push/Services/IPushNotificationService.cs index 60f3c35089..58b8a4722d 100644 --- a/src/Core/Platform/Push/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/IPushNotificationService.cs @@ -2,41 +2,410 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.Models; using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; +using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push; public interface IPushNotificationService { - Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds); - Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds); - Task PushSyncCipherDeleteAsync(Cipher cipher); - Task PushSyncFolderCreateAsync(Folder folder); - Task PushSyncFolderUpdateAsync(Folder folder); - Task PushSyncFolderDeleteAsync(Folder folder); - Task PushSyncCiphersAsync(Guid userId); - Task PushSyncVaultAsync(Guid userId); - Task PushSyncOrganizationsAsync(Guid userId); - Task PushSyncOrgKeysAsync(Guid userId); - Task PushSyncSettingsAsync(Guid userId); - Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false); - Task PushSyncSendCreateAsync(Send send); - Task PushSyncSendUpdateAsync(Send send); - Task PushSyncSendDeleteAsync(Send send); - Task PushNotificationAsync(Notification notification); - Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus); - Task PushAuthRequestAsync(AuthRequest authRequest); - Task PushAuthRequestResponseAsync(AuthRequest authRequest); - Task PushSyncOrganizationStatusAsync(Organization organization); - Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization); + Guid InstallationId { get; } + TimeProvider TimeProvider { get; } + ILogger Logger { get; } - Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null); - Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null); - Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null); - Task PushPendingSecurityTasksAsync(Guid userId); + #region Legacy method, to be removed soon. + Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) + => PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); + + Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) + => PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); + + Task PushSyncCipherDeleteAsync(Cipher cipher) + => PushCipherAsync(cipher, PushType.SyncLoginDelete, null); + + Task PushSyncFolderCreateAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderCreate, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncFolderUpdateAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderUpdate, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncFolderDeleteAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderDelete, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncCiphersAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncCiphers, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncVaultAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncVault, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrganizationsAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizations, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrgKeysAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrgKeys, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncSettingsAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncSettings, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false) + => PushAsync(new PushNotification + { + Type = PushType.LogOut, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = excludeCurrentContextFromPush, + }); + + Task PushSyncSendCreateAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendCreate, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + Task PushSyncSendUpdateAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendUpdate, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + Task PushSyncSendDeleteAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendDelete, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + Task PushNotificationAsync(Notification notification) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + InstallationId = notification.Global ? InstallationId : null, + TaskId = notification.TaskId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + }; + + NotificationTarget target; + Guid targetId; + + if (notification.Global) + { + // TODO: Think about this a bit more + target = NotificationTarget.Installation; + targetId = InstallationId; + } + else if (notification.UserId.HasValue) + { + target = NotificationTarget.User; + targetId = notification.UserId.Value; + } + else if (notification.OrganizationId.HasValue) + { + target = NotificationTarget.Organization; + targetId = notification.OrganizationId.Value; + } + else + { + Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); + return Task.CompletedTask; + } + + return PushAsync(new PushNotification + { + Type = PushType.Notification, + Target = target, + TargetId = targetId, + Payload = message, + ExcludeCurrentContext = true, + ClientType = notification.ClientType, + }); + } + + Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + InstallationId = notification.Global ? InstallationId : null, + TaskId = notification.TaskId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate, + }; + + NotificationTarget target; + Guid targetId; + + if (notification.Global) + { + // TODO: Think about this a bit more + target = NotificationTarget.Installation; + targetId = InstallationId; + } + else if (notification.UserId.HasValue) + { + target = NotificationTarget.User; + targetId = notification.UserId.Value; + } + else if (notification.OrganizationId.HasValue) + { + target = NotificationTarget.Organization; + targetId = notification.OrganizationId.Value; + } + else + { + Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); + return Task.CompletedTask; + } + + return PushAsync(new PushNotification + { + Type = PushType.NotificationStatus, + Target = target, + TargetId = targetId, + Payload = message, + ExcludeCurrentContext = true, + ClientType = notification.ClientType, + }); + } + + Task PushAuthRequestAsync(AuthRequest authRequest) + => PushAsync(new PushNotification + { + Type = PushType.AuthRequest, + Target = NotificationTarget.User, + TargetId = authRequest.UserId, + Payload = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId, + }, + ExcludeCurrentContext = true, + }); + + Task PushAuthRequestResponseAsync(AuthRequest authRequest) + => PushAsync(new PushNotification + { + Type = PushType.AuthRequestResponse, + Target = NotificationTarget.User, + TargetId = authRequest.UserId, + Payload = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncOrganizationStatusAsync(Organization organization) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizationStatusChanged, + Target = NotificationTarget.Organization, + TargetId = organization.Id, + Payload = new OrganizationStatusPushNotification + { + OrganizationId = organization.Id, + Enabled = organization.Enabled, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizationCollectionSettingChanged, + Target = NotificationTarget.Organization, + TargetId = organization.Id, + Payload = new OrganizationCollectionManagementPushNotification + { + OrganizationId = organization.Id, + LimitCollectionCreation = organization.LimitCollectionCreation, + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion, + }, + ExcludeCurrentContext = false, + }); + + Task PushPendingSecurityTasksAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.PendingSecurityTasks, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + #endregion + + Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds); + + Task PushAsync(PushNotification pushNotification) + where T : class; } diff --git a/src/Core/Platform/Push/Services/IPushRelayer.cs b/src/Core/Platform/Push/Services/IPushRelayer.cs new file mode 100644 index 0000000000..fde0a521f3 --- /dev/null +++ b/src/Core/Platform/Push/Services/IPushRelayer.cs @@ -0,0 +1,44 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.Enums; + +namespace Bit.Core.Platform.Push.Internal; + +/// +/// An object encapsulating the information that is available in a notification +/// given to us from a self-hosted installation. +/// +public class RelayedNotification +{ + /// + public required PushType Type { get; init; } + /// + public required NotificationTarget Target { get; init; } + /// + public required Guid TargetId { get; init; } + /// + public required JsonElement Payload { get; init; } + /// + public required ClientType? ClientType { get; init; } + public required Guid? DeviceId { get; init; } + public required string? Identifier { get; init; } +} + +/// +/// A service for taking a notification that was relayed to us from a self-hosted installation and +/// will be injested into our infrastructure so that we can get the notification to devices that require +/// cloud interaction. +/// +/// +/// This interface should be treated as internal and not consumed by other teams. +/// +public interface IPushRelayer +{ + /// + /// Relays a notification that was received from an authenticated installation into our cloud push notification infrastructure. + /// + /// The authenticated installation this notification came from. + /// The information received from the self-hosted installation. + Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification); +} diff --git a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs index 490b690a3b..404b153fa3 100644 --- a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs @@ -1,202 +1,77 @@ #nullable enable -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Enums; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; public class MultiServicePushNotificationService : IPushNotificationService { - private readonly IEnumerable _services; - private readonly ILogger _logger; + private readonly IEnumerable _services; + + public Guid InstallationId { get; } + + public TimeProvider TimeProvider { get; } + + public ILogger Logger { get; } public MultiServicePushNotificationService( - [FromKeyedServices("implementation")] IEnumerable services, + IEnumerable services, ILogger logger, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + TimeProvider timeProvider) { _services = services; - _logger = logger; - _logger.LogInformation("Hub services: {Services}", _services.Count()); + Logger = logger; + Logger.LogInformation("Hub services: {Services}", _services.Count()); globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub => { - _logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); + Logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); }); + InstallationId = globalSettings.Installation.Id; + TimeProvider = timeProvider; } - public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - PushToServices((s) => s.PushSyncCipherCreateAsync(cipher, collectionIds)); - return Task.FromResult(0); - } - - public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - PushToServices((s) => s.PushSyncCipherUpdateAsync(cipher, collectionIds)); - return Task.FromResult(0); - } - - public Task PushSyncCipherDeleteAsync(Cipher cipher) - { - PushToServices((s) => s.PushSyncCipherDeleteAsync(cipher)); - return Task.FromResult(0); - } - - public Task PushSyncFolderCreateAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderCreateAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncFolderUpdateAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderUpdateAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncFolderDeleteAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderDeleteAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncCiphersAsync(Guid userId) - { - PushToServices((s) => s.PushSyncCiphersAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncVaultAsync(Guid userId) - { - PushToServices((s) => s.PushSyncVaultAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationsAsync(Guid userId) - { - PushToServices((s) => s.PushSyncOrganizationsAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncOrgKeysAsync(Guid userId) - { - PushToServices((s) => s.PushSyncOrgKeysAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncSettingsAsync(Guid userId) - { - PushToServices((s) => s.PushSyncSettingsAsync(userId)); - return Task.FromResult(0); - } - - public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - PushToServices((s) => s.PushLogOutAsync(userId, excludeCurrentContext)); - return Task.FromResult(0); - } - - public Task PushSyncSendCreateAsync(Send send) - { - PushToServices((s) => s.PushSyncSendCreateAsync(send)); - return Task.FromResult(0); - } - - public Task PushSyncSendUpdateAsync(Send send) - { - PushToServices((s) => s.PushSyncSendUpdateAsync(send)); - return Task.FromResult(0); - } - - public Task PushAuthRequestAsync(AuthRequest authRequest) - { - PushToServices((s) => s.PushAuthRequestAsync(authRequest)); - return Task.FromResult(0); - } - - public Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - PushToServices((s) => s.PushAuthRequestResponseAsync(authRequest)); - return Task.FromResult(0); - } - - public Task PushSyncSendDeleteAsync(Send send) - { - PushToServices((s) => s.PushSyncSendDeleteAsync(send)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationStatusAsync(Organization organization) - { - PushToServices((s) => s.PushSyncOrganizationStatusAsync(organization)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) - { - PushToServices(s => s.PushSyncOrganizationCollectionManagementSettingsAsync(organization)); - return Task.CompletedTask; - } - - public Task PushNotificationAsync(Notification notification) - { - PushToServices((s) => s.PushNotificationAsync(notification)); - return Task.CompletedTask; - } - - public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - PushToServices((s) => s.PushNotificationStatusAsync(notification, notificationStatus)); - return Task.CompletedTask; - } - - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - PushToServices((s) => - s.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType)); - return Task.CompletedTask; - } - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType)); - return Task.FromResult(0); - } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType)); - return Task.FromResult(0); - } - - public Task PushPendingSecurityTasksAsync(Guid userId) - { - PushToServices((s) => s.PushPendingSecurityTasksAsync(userId)); - return Task.CompletedTask; - } - - private void PushToServices(Func pushFunc) + private Task PushToServices(Func pushFunc) { if (!_services.Any()) { - _logger.LogWarning("No services found to push notification"); - return; + Logger.LogWarning("No services found to push notification"); + return Task.CompletedTask; } + +#if DEBUG + var tasks = new List(); +#endif + foreach (var service in _services) { - _logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); + Logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); +#if DEBUG + var task = +#endif pushFunc(service); +#if DEBUG + tasks.Add(task); +#endif } + +#if DEBUG + return Task.WhenAll(tasks); +#else + return Task.CompletedTask; +#endif + } + + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) + { + return PushToServices((s) => s.PushCipherAsync(cipher, pushType, collectionIds)); + } + public Task PushAsync(PushNotification pushNotification) where T : class + { + return PushToServices((s) => s.PushAsync(pushNotification)); } } diff --git a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs index 6e7278cf94..e6f71de006 100644 --- a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs @@ -1,129 +1,12 @@ #nullable enable -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Enums; -using Bit.Core.NotificationCenter.Entities; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; namespace Bit.Core.Platform.Push.Internal; -public class NoopPushNotificationService : IPushNotificationService +internal class NoopPushNotificationService : IPushEngine { - public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - return Task.FromResult(0); - } + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) => Task.CompletedTask; - public Task PushSyncCipherDeleteAsync(Cipher cipher) - { - return Task.FromResult(0); - } - - public Task PushSyncCiphersAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderCreateAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderDeleteAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderUpdateAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationsAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncOrgKeysAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncSettingsAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncVaultAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - return Task.FromResult(0); - } - - public Task PushSyncSendCreateAsync(Send send) - { - return Task.FromResult(0); - } - - public Task PushSyncSendDeleteAsync(Send send) - { - return Task.FromResult(0); - } - - public Task PushSyncSendUpdateAsync(Send send) - { - return Task.FromResult(0); - } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationStatusAsync(Organization organization) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => Task.CompletedTask; - - public Task PushAuthRequestAsync(AuthRequest authRequest) - { - return Task.FromResult(0); - } - - public Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - return Task.FromResult(0); - } - - public Task PushNotificationAsync(Notification notification) => Task.CompletedTask; - - public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) => - Task.CompletedTask; - - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - return Task.FromResult(0); - } - - public Task PushPendingSecurityTasksAsync(Guid userId) - { - return Task.FromResult(0); - } + public Task PushAsync(PushNotification pushNotification) where T : class => Task.CompletedTask; } diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index bdeefc0363..5e0d584ba8 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -1,13 +1,9 @@ #nullable enable -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -20,18 +16,15 @@ namespace Bit.Core.Platform.Push; /// Used by Cloud-Hosted environments. /// Received by AzureQueueHostedService message receiver in Notifications project. /// -public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService +public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushEngine { - private readonly IGlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly TimeProvider _timeProvider; public NotificationsApiPushNotificationService( IHttpClientFactory httpFactory, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger, - TimeProvider timeProvider) + ILogger logger) : base( httpFactory, globalSettings.BaseServiceUri.InternalNotifications, @@ -41,27 +34,10 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService globalSettings.InternalIdentityKey, logger) { - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; - _timeProvider = timeProvider; } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -89,174 +65,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService } } - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification - { - UserId = userId, - Date = _timeProvider.GetUtcNow().UtcDateTime, - }; - - await SendMessageAsync(type, message, excludeCurrentContext); - } - - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - public async Task PushNotificationAsync(Notification notification) - { - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - await SendMessageAsync(PushType.Notification, message, true); - } - - public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - await SendMessageAsync(PushType.NotificationStatus, message, true); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification - { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendMessageAsync(type, message, false); - } - } - private async Task SendMessageAsync(PushType type, T payload, bool excludeCurrentContext) { var contextId = GetContextIdentifier(excludeCurrentContext); @@ -276,43 +84,8 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService return currentContext?.DeviceIdentifier; } - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - // Noop - Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) + public async Task PushAsync(PushNotification pushNotification) where T : class { - // Noop - return Task.FromResult(0); + await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext); } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - // Noop - return Task.FromResult(0); - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, false); } diff --git a/src/Core/Platform/Push/Services/PushNotification.cs b/src/Core/Platform/Push/Services/PushNotification.cs new file mode 100644 index 0000000000..e1d3f44cd8 --- /dev/null +++ b/src/Core/Platform/Push/Services/PushNotification.cs @@ -0,0 +1,78 @@ +#nullable enable +using Bit.Core.Enums; + +namespace Bit.Core.Platform.Push; + +/// +/// Contains constants for all the available targets for a given notification. +/// +public enum NotificationTarget +{ + /// + /// The target for the notification is a single user. + /// + User, + /// + /// The target for the notification are all the users in an organization. + /// + Organization, + /// + /// The target for the notification are all the organizations, + /// and all the users in that organization for a installation. + /// + Installation, +} + +/// +/// An object containing all the information required for getting a notification +/// to an end users device and the information you want available to that device. +/// +/// The type of the payload. This type is expected to be able to be roundtripped as JSON. +public record PushNotification + where T : class +{ + /// + /// The to be associated with the notification. This is used to route + /// the notification to the correct handler on the client side. Be sure to use the correct payload + /// type for the associated . + /// + public required PushType Type { get; init; } + + /// + /// The target entity type for the notification. + /// + /// + /// When the target type is the + /// property is expected to be a users ID. When it is + /// it should be an organizations id. When it is a + /// it should be an installation id. + /// + public required NotificationTarget Target { get; init; } + + /// + /// The indentifier for the given . + /// + public required Guid TargetId { get; init; } + + /// + /// The payload to be sent with the notification. This object will be JSON serialized. + /// + public required T Payload { get; init; } + + /// + /// When the notification will not include the current context identifier on it, this + /// means that the notification may get handled on the device that this notification could have originated from. + /// + public required bool ExcludeCurrentContext { get; init; } + + /// + /// The type of clients the notification should be sent to, if then + /// is inferred. + /// + public ClientType? ClientType { get; init; } + + internal Guid? GetTargetWhen(NotificationTarget notificationTarget) + { + return Target == notificationTarget ? TargetId : null; + } +} diff --git a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs index 0ede81e719..9f2289b864 100644 --- a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs @@ -1,18 +1,15 @@ #nullable enable -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models; using Bit.Core.Models.Api; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; @@ -22,20 +19,18 @@ namespace Bit.Core.Platform.Push.Internal; /// Used by Self-Hosted environments. /// Received by PushController endpoint in Api project. /// -public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService +public class RelayPushNotificationService : BaseIdentityClientService, IPushEngine { private readonly IDeviceRepository _deviceRepository; - private readonly IGlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly TimeProvider _timeProvider; + public RelayPushNotificationService( IHttpClientFactory httpFactory, IDeviceRepository deviceRepository, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger, - TimeProvider timeProvider) + ILogger logger) : base( httpFactory, globalSettings.PushRelayBaseUri, @@ -46,27 +41,10 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti logger) { _deviceRepository = deviceRepository; - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; - _timeProvider = timeProvider; } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -87,306 +65,45 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti RevisionDate = cipher.RevisionDate, }; - await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true); - } - } - - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendPayloadToUserAsync(folder.UserId, type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification + await PushAsync(new PushNotification { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendPayloadToUserAsync(message.UserId, type, message, true); + Type = type, + Target = NotificationTarget.User, + TargetId = cipher.UserId.Value, + Payload = message, + ExcludeCurrentContext = true, + }); } } - public async Task PushAuthRequestAsync(AuthRequest authRequest) + public async Task PushAsync(PushNotification pushNotification) + where T : class { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } + var deviceIdentifier = _httpContextAccessor.HttpContext + ?.RequestServices.GetService() + ?.DeviceIdentifier; - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } + Guid? deviceId = null; - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendPayloadToUserAsync(authRequest.UserId, type, message, true); - } - - public async Task PushNotificationAsync(Notification notification) - { - var message = new NotificationPushNotification + if (!string.IsNullOrEmpty(deviceIdentifier)) { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate + var device = await _deviceRepository.GetByIdentifierAsync(deviceIdentifier); + deviceId = device?.Id; + } + + var payload = new PushSendRequestModel + { + Type = pushNotification.Type, + UserId = pushNotification.GetTargetWhen(NotificationTarget.User), + OrganizationId = pushNotification.GetTargetWhen(NotificationTarget.Organization), + InstallationId = pushNotification.GetTargetWhen(NotificationTarget.Installation), + Payload = pushNotification.Payload, + Identifier = pushNotification.ExcludeCurrentContext ? deviceIdentifier : null, + // We set the device id regardless of if they want to exclude the current context or not + DeviceId = deviceId, + ClientType = pushNotification.ClientType, }; - if (notification.Global) - { - await SendPayloadToInstallationAsync(PushType.Notification, message, true, notification.ClientType); - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); - } - } - - public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - if (notification.Global) - { - await SendPayloadToInstallationAsync(PushType.NotificationStatus, message, true, notification.ClientType); - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); - } - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendPayloadToOrganizationAsync( - organization.Id, - PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, - false - ); - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task SendPayloadToInstallationAsync(PushType type, object payload, bool excludeCurrentContext, - ClientType? clientType = null) - { - var request = new PushSendRequestModel - { - InstallationId = _globalSettings.Installation.Id.ToString(), - Type = type, - Payload = payload, - ClientType = clientType - }; - - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, - ClientType? clientType = null) - { - var request = new PushSendRequestModel - { - UserId = userId.ToString(), - Type = type, - Payload = payload, - ClientType = clientType - }; - - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, - bool excludeCurrentContext, ClientType? clientType = null) - { - var request = new PushSendRequestModel - { - OrganizationId = orgId.ToString(), - Type = type, - Payload = payload, - ClientType = clientType - }; - - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) - { - var currentContext = - _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; - if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) - { - var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); - if (device != null) - { - request.DeviceId = device.Id.ToString(); - } - - if (addIdentifier) - { - request.Identifier = currentContext.DeviceIdentifier; - } - } - } - - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - throw new NotImplementedException(); - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - throw new NotImplementedException(); - } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - throw new NotImplementedException(); + await SendAsync(HttpMethod.Post, "push/send", payload); } } diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index f8f8381cc0..871a1be038 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -288,7 +288,7 @@ public static class ServiceCollectionExtensions if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) && CoreHelpers.SettingHasValue(globalSettings.Installation.Key)) { - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.AddSingleton(); } else @@ -299,20 +299,20 @@ public static class ServiceCollectionExtensions if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) { - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } } else { services.AddSingleton(); services.AddSingleton(); - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddSingleton(); if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) { services.AddKeyedSingleton("notifications", (_, _) => new QueueClient(globalSettings.Notifications.ConnectionString, "notifications")); - services.AddKeyedSingleton( - "implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } } @@ -366,7 +366,6 @@ public static class ServiceCollectionExtensions { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs b/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs index 08c5973936..173580ad8c 100644 --- a/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs +++ b/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs @@ -28,6 +28,8 @@ public class ApiApplicationFactory : WebApplicationFactoryBase _identityApplicationFactory.ManagesDatabase = false; } + public IdentityApplicationFactory Identity => _identityApplicationFactory; + protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); diff --git a/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs new file mode 100644 index 0000000000..4d86817a11 --- /dev/null +++ b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs @@ -0,0 +1,449 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Nodes; +using Azure.Storage.Queues; +using Bit.Api.IntegrationTest.Factories; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Models.Api; +using Bit.Core.Models.Data; +using Bit.Core.NotificationHub; +using Bit.Core.Platform.Installations; +using Bit.Core.Repositories; +using NSubstitute; +using Xunit; +using static Bit.Core.Settings.GlobalSettings; + +namespace Bit.Api.IntegrationTest.Platform.Controllers; + +public class PushControllerTests +{ + private static readonly Guid _userId = Guid.NewGuid(); + private static readonly Guid _organizationId = Guid.NewGuid(); + private static readonly Guid _deviceId = Guid.NewGuid(); + + public static IEnumerable SendData() + { + static object[] Typed(PushSendRequestModel pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall = true) + { + return [pushSendRequestModel, expectedHubTagExpression, expectHubCall]; + } + + static object[] UserTyped(PushType pushType) + { + return Typed(new PushSendRequestModel + { + Type = pushType, + UserId = _userId, + DeviceId = _deviceId, + Payload = new UserPushNotification + { + Date = DateTime.UtcNow, + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + } + + // User cipher + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherUpdate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherUpdate, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherCreate, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherDelete, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherDelete, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderDelete, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return UserTyped(PushType.SyncCiphers); + yield return UserTyped(PushType.SyncVault); + yield return UserTyped(PushType.SyncOrganizations); + yield return UserTyped(PushType.SyncOrgKeys); + yield return UserTyped(PushType.SyncSettings); + yield return UserTyped(PushType.LogOut); + yield return UserTyped(PushType.PendingSecurityTasks); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.AuthRequest, + UserId = _userId, + DeviceId = _deviceId, + Payload = new AuthRequestPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.AuthRequestResponse, + UserId = _userId, + DeviceId = _deviceId, + Payload = new AuthRequestPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.Notification, + UserId = _userId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.Notification, + UserId = _userId, + DeviceId = _deviceId, + ClientType = ClientType.All, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + Global = true, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.NotificationStatus, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.NotificationStatus, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + } + + [Theory] + [MemberData(nameof(SendData))] + public async Task Send_Works(PushSendRequestModel pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall) + { + var (apiFactory, httpClient, installation, queueClient, notificationHubProxy) = await SetupTest(); + + // Act + var pushSendResponse = await httpClient.PostAsJsonAsync("push/send", pushSendRequestModel); + + // Assert + pushSendResponse.EnsureSuccessStatusCode(); + + // Relayed notifications, the ones coming to this endpoint should + // not make their way into our Azure Queue and instead should only be sent to Azure Notifications + // hub. + await queueClient + .Received(0) + .SendMessageAsync(Arg.Any()); + + // Check that this notification was sent through hubs the expected number of times + await notificationHubProxy + .Received(expectHubCall ? 1 : 0) + .SendTemplateNotificationAsync( + Arg.Any>(), + Arg.Is(expectedHubTagExpression.Replace("%installation%", installation.Id.ToString())) + ); + + // TODO: Expect on the dictionary more? + + // Notifications being relayed from SH should have the device id + // tracked so that we can later send the notification to that device. + await apiFactory.GetService() + .Received(1) + .UpsertAsync(Arg.Is( + ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == pushSendRequestModel.DeviceId.ToString() + )); + } + + [Fact] + public async Task Send_InstallationNotification_NotAuthenticatedInstallation_Fails() + { + var (_, httpClient, _, _, _) = await SetupTest(); + + var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.NotificationStatus, + InstallationId = Guid.NewGuid(), + Payload = new { } + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.Content.ReadFromJsonAsync(); + Assert.Equal(JsonValueKind.Object, body.GetValueKind()); + Assert.True(body.AsObject().TryGetPropertyValue("message", out var message)); + Assert.Equal(JsonValueKind.String, message.GetValueKind()); + Assert.Equal("InstallationId does not match current context.", message.GetValue()); + } + + [Fact] + public async Task Send_InstallationNotification_Works() + { + var (apiFactory, httpClient, installation, _, notificationHubProxy) = await SetupTest(); + + var deviceId = Guid.NewGuid(); + + var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.NotificationStatus, + InstallationId = installation.Id, + Payload = new { }, + DeviceId = deviceId, + ClientType = ClientType.Web, + }); + + response.EnsureSuccessStatusCode(); + + await notificationHubProxy + .Received(1) + .SendTemplateNotificationAsync( + Arg.Any>(), + Arg.Is($"(template:payload && installationId:{installation.Id} && clientType:Web)") + ); + + await apiFactory.GetService() + .Received(1) + .UpsertAsync(Arg.Is( + ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == deviceId.ToString() + )); + } + + [Fact] + public async Task Send_NoOrganizationNoInstallationNoUser_FailsModelValidation() + { + var (_, client, _, _, _) = await SetupTest(); + + var response = await client.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.AuthRequest, + Payload = new { }, + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.Content.ReadFromJsonAsync(); + Assert.Equal(JsonValueKind.Object, body.GetValueKind()); + Assert.True(body.AsObject().TryGetPropertyValue("message", out var message)); + Assert.Equal(JsonValueKind.String, message.GetValueKind()); + Assert.Equal("The model state is invalid.", message.GetValue()); + } + + private static async Task<(ApiApplicationFactory Factory, HttpClient AuthedClient, Installation Installation, QueueClient MockedQueue, INotificationHubProxy MockedHub)> SetupTest() + { + // Arrange + var apiFactory = new ApiApplicationFactory(); + + var queueClient = Substitute.For(); + + // Substitute the underlying queue messages will go to. + apiFactory.ConfigureServices(services => + { + var queueClientService = services.FirstOrDefault( + sd => sd.ServiceKey == (object)"notifications" + && sd.ServiceType == typeof(QueueClient) + ) ?? throw new InvalidOperationException("Expected service was not found."); + + services.Remove(queueClientService); + + services.AddKeyedSingleton("notifications", queueClient); + }); + + var notificationHubProxy = Substitute.For(); + + apiFactory.SubstituteService(s => + { + s.AllClients + .Returns(notificationHubProxy); + }); + + apiFactory.SubstituteService(s => { }); + + // Setup as cloud with NotificationHub setup and Azure Queue + apiFactory.UpdateConfiguration("GlobalSettings:Notifications:ConnectionString", "any_value"); + + // Configure hubs + var index = 0; + void AddHub(NotificationHubSettings notificationHubSettings) + { + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:ConnectionString", + notificationHubSettings.ConnectionString + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:HubName", + notificationHubSettings.HubName + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationStartDate", + notificationHubSettings.RegistrationStartDate?.ToString() + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationEndDate", + notificationHubSettings.RegistrationEndDate?.ToString() + ); + index++; + } + + AddHub(new NotificationHubSettings + { + ConnectionString = "some_value", + RegistrationStartDate = DateTime.UtcNow.AddDays(-2), + }); + + var httpClient = apiFactory.CreateClient(); + + // Add installation into database + var installationRepository = apiFactory.GetService(); + var installation = await installationRepository.CreateAsync(new Installation + { + Key = "my_test_key", + Email = "test@example.com", + Enabled = true, + }); + + var identityClient = apiFactory.Identity.CreateDefaultClient(); + + var connectTokenResponse = await identityClient.PostAsync("connect/token", new FormUrlEncodedContent(new Dictionary + { + { "grant_type", "client_credentials" }, + { "scope", "api.push" }, + { "client_id", $"installation.{installation.Id}" }, + { "client_secret", installation.Key }, + })); + + connectTokenResponse.EnsureSuccessStatusCode(); + + var connectTokenResponseModel = await connectTokenResponse.Content.ReadFromJsonAsync(); + + // Setup authentication + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + connectTokenResponseModel["token_type"].GetValue(), + connectTokenResponseModel["access_token"].GetValue() + ); + + return (apiFactory, httpClient, installation, queueClient, notificationHubProxy); + } +} diff --git a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs index 6df09c17dc..d6a26255e9 100644 --- a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs +++ b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs @@ -18,210 +18,6 @@ namespace Bit.Api.Test.Platform.Push.Controllers; [SutProviderCustomize] public class PushControllerTests { - [Theory] - [BitAutoData(false, true)] - [BitAutoData(false, false)] - [BitAutoData(true, true)] - public async Task SendAsync_InstallationIdNotSetOrSelfHosted_BadRequest(bool haveInstallationId, bool selfHosted, - SutProvider sutProvider, Guid installationId, Guid userId, Guid organizationId) - { - sutProvider.GetDependency().SelfHosted = selfHosted; - if (haveInstallationId) - { - sutProvider.GetDependency().InstallationId.Returns(installationId); - } - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = userId.ToString(), - OrganizationId = organizationId.ToString(), - InstallationId = installationId.ToString(), - Payload = "test-payload" - })); - - Assert.Equal("Not correctly configured for push relays.", exception.Message); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [BitAutoData] - public async Task SendAsync_UserIdAndOrganizationIdAndInstallationIdEmpty_NoPushNotificationSent( - SutProvider sutProvider, Guid installationId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = null, - Payload = "test-payload" - }); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true], [false, true])] - public async Task SendAsync_UserIdSet_SendPayloadToUserAsync(bool haveIdentifier, bool haveDeviceId, - bool haveOrganizationId, SutProvider sutProvider, Guid installationId, Guid userId, - Guid identifier, Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedUserId = $"{installationId}_{userId}"; - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = userId.ToString(), - OrganizationId = haveOrganizationId ? Guid.NewGuid().ToString() : null, - InstallationId = null, - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToUserAsync(expectedUserId, PushType.Notification, "test-payload", expectedIdentifier, - expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true])] - public async Task SendAsync_OrganizationIdSet_SendPayloadToOrganizationAsync(bool haveIdentifier, bool haveDeviceId, - SutProvider sutProvider, Guid installationId, Guid organizationId, Guid identifier, - Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedOrganizationId = $"{installationId}_{organizationId}"; - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = organizationId.ToString(), - InstallationId = null, - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToOrganizationAsync(expectedOrganizationId, PushType.Notification, "test-payload", - expectedIdentifier, expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true])] - public async Task SendAsync_InstallationIdSet_SendPayloadToInstallationAsync(bool haveIdentifier, bool haveDeviceId, - SutProvider sutProvider, Guid installationId, Guid identifier, Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = installationId.ToString(), - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToInstallationAsync(installationId.ToString(), PushType.Notification, "test-payload", - expectedIdentifier, expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - } - - [Theory] - [BitAutoData] - public async Task SendAsync_InstallationIdNotMatching_BadRequest(SutProvider sutProvider, - Guid installationId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = Guid.NewGuid().ToString(), - Payload = "test-payload", - DeviceId = null, - Identifier = null, - ClientType = ClientType.All, - })); - - Assert.Equal("InstallationId does not match current context.", exception.Message); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - } - [Theory] [BitAutoData(false, true)] [BitAutoData(false, false)] diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs index 2d3dbffcf6..e372899599 100644 --- a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs +++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs @@ -11,16 +11,14 @@ namespace Bit.Core.Test.Models.Api.Request; public class PushSendRequestModelTests { - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "], [null, "", " "])] - public void Validate_UserIdOrganizationIdInstallationIdNullOrEmpty_Invalid(string? userId, string? organizationId, - string? installationId) + [Fact] + public void Validate_UserIdOrganizationIdInstallationIdNull_Invalid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = organizationId, - InstallationId = installationId, + UserId = null, + OrganizationId = null, + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -32,16 +30,14 @@ public class PushSendRequestModelTests result => result.ErrorMessage == "UserId or OrganizationId or InstallationId is required."); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_UserIdProvidedOrganizationIdInstallationIdNullOrEmpty_Valid(string? organizationId, - string? installationId) + [Fact] + public void Validate_UserIdProvidedOrganizationIdInstallationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = organizationId, - InstallationId = installationId, + UserId = Guid.NewGuid(), + OrganizationId = null, + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -51,16 +47,14 @@ public class PushSendRequestModelTests Assert.Empty(results); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_OrganizationIdProvidedUserIdInstallationIdNullOrEmpty_Valid(string? userId, - string? installationId) + [Fact] + public void Validate_OrganizationIdProvidedUserIdInstallationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = Guid.NewGuid().ToString(), - InstallationId = installationId, + UserId = null, + OrganizationId = Guid.NewGuid(), + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -70,16 +64,14 @@ public class PushSendRequestModelTests Assert.Empty(results); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_InstallationIdProvidedUserIdOrganizationIdNullOrEmpty_Valid(string? userId, - string? organizationId) + [Fact] + public void Validate_InstallationIdProvidedUserIdOrganizationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = organizationId, - InstallationId = Guid.NewGuid().ToString(), + UserId = null, + OrganizationId = null, + InstallationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test" }; @@ -94,10 +86,10 @@ public class PushSendRequestModelTests [BitAutoData("Type")] public void Validate_RequiredFieldNotProvided_Invalid(string requiredField) { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = Guid.NewGuid().ToString(), + UserId = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test" }; @@ -115,7 +107,7 @@ public class PushSendRequestModelTests var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull); var jsonException = - Assert.Throws(() => JsonSerializer.Deserialize(serialized)); + Assert.Throws(() => JsonSerializer.Deserialize>(serialized)); Assert.Contains($"missing required properties, including the following: {requiredField}", jsonException.Message); } @@ -123,15 +115,15 @@ public class PushSendRequestModelTests [Fact] public void Validate_AllFieldsPresent_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = Guid.NewGuid().ToString(), + UserId = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test payload", Identifier = Guid.NewGuid().ToString(), ClientType = ClientType.All, - DeviceId = Guid.NewGuid().ToString() + DeviceId = Guid.NewGuid() }; var results = Validate(model); @@ -139,7 +131,7 @@ public class PushSendRequestModelTests Assert.Empty(results); } - private static List Validate(PushSendRequestModel model) + private static List Validate(PushSendRequestModel model) { var results = new List(); Validator.TryValidateObject(model, new ValidationContext(model), results, true); diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index 6f4ea9ca12..54a6f84339 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -5,12 +5,11 @@ using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; -using Bit.Core.Models.Data; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; -using Bit.Core.Settings; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -33,483 +32,6 @@ public class NotificationHubPushNotificationServiceTests private static readonly DateTime _now = DateTime.UtcNow; private static readonly Guid _installationId = Guid.Parse("da73177b-513f-4444-b582-595c890e1022"); - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdDefault_NotSent( - SutProvider sutProvider, Notification notification) - { - sutProvider.GetDependency().Installation.Id = default; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency() - .Received(0) - .AllClients - .Received(0) - .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId( - SutProvider sutProvider, Notification notification, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, null, installationId); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && installationId:{installationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType( - ClientType clientType, SutProvider sutProvider, - Notification notification, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, null, installationId); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && installationId:{installationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(false)] - [BitAutoData(true)] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( - bool organizationIdNull, SutProvider sutProvider, - Notification notification) - { - if (organizationIdNull) - { - notification.OrganizationId = null; - } - - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload_userId:{notification.UserId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( - ClientType clientType, SutProvider sutProvider, - Notification notification) - { - notification.OrganizationId = null; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( - ClientType clientType, SutProvider sutProvider, - Notification notification) - { - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( - SutProvider sutProvider, Notification notification) - { - notification.UserId = null; - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && organizationId:{notification.OrganizationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( - ClientType clientType, SutProvider sutProvider, - Notification notification) - { - notification.UserId = null; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationStatusAsync_GlobalInstallationIdDefault_NotSent( - SutProvider sutProvider, Notification notification, - NotificationStatus notificationStatus) - { - sutProvider.GetDependency().Installation.Id = default; - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency() - .Received(0) - .AllClients - .Received(0) - .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId( - SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = ClientType.All; - - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && installationId:{installationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize] - public async Task - PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType( - ClientType clientType, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = clientType; - - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && installationId:{installationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(false)] - [BitAutoData(true)] - [NotificationCustomize(false)] - public async Task PushNotificationStatusAsync_UserIdProvidedClientTypeAll_SentToUser( - bool organizationIdNull, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus) - { - if (organizationIdNull) - { - notification.OrganizationId = null; - } - - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload_userId:{notification.UserId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( - ClientType clientType, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus) - { - notification.OrganizationId = null; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( - ClientType clientType, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus) - { - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - public async Task PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( - SutProvider sutProvider, Notification notification, - NotificationStatus notificationStatus) - { - notification.UserId = null; - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && organizationId:{notification.OrganizationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize(false)] - public async Task - PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( - ClientType clientType, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus) - { - notification.UserId = null; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData([null])] - [BitAutoData(ClientType.All)] - public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, - SutProvider sutProvider, Guid userId, PushType pushType, string payload, - string identifier) - { - await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, - clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Mobile)] - [BitAutoData(ClientType.Web)] - public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, - SutProvider sutProvider, Guid userId, PushType pushType, string payload, - string identifier) - { - await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, - clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData([null])] - [BitAutoData(ClientType.All)] - public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, - SutProvider sutProvider, Guid organizationId, PushType pushType, - string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Mobile)] - [BitAutoData(ClientType.Web)] - public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( - ClientType clientType, SutProvider sutProvider, Guid organizationId, - PushType pushType, string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData([null])] - [BitAutoData(ClientType.All)] - public async Task SendPayloadToInstallationAsync_ClientTypeNullOrAll_SentToInstallation(ClientType? clientType, - SutProvider sutProvider, Guid installationId, PushType pushType, - string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Mobile)] - [BitAutoData(ClientType.Web)] - public async Task SendPayloadToInstallationAsync_ClientTypeExplicit_SentToInstallationAndClientType( - ClientType clientType, SutProvider sutProvider, Guid installationId, - PushType pushType, string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - [Fact] public async Task PushSyncCipherCreateAsync_SendExpectedData() { @@ -1066,7 +588,7 @@ public class NotificationHubPushNotificationServiceTests ); } - private async Task VerifyNotificationAsync(Func test, + private async Task VerifyNotificationAsync(Func test, PushType type, JsonNode expectedPayload, string tag) { var installationDeviceRepository = Substitute.For(); @@ -1104,12 +626,11 @@ public class NotificationHubPushNotificationServiceTests notificationHubPool, httpContextAccessor, NullLogger.Instance, - globalSettings, - fakeTimeProvider + globalSettings ); // Act - await test(sut); + await test(new EngineWrapper(sut, fakeTimeProvider, _installationId)); // Assert var calls = notificationHubProxy.ReceivedCalls(); diff --git a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs index e57544b48a..4e2ec19086 100644 --- a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs @@ -9,14 +9,11 @@ using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; +using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; -using Bit.Core.Settings; using Bit.Core.Test.AutoFixture; -using Bit.Core.Test.AutoFixture.CurrentContextFixtures; -using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -42,96 +39,6 @@ public class AzureQueuePushNotificationServiceTests _fakeTimeProvider.SetUtcNow(DateTime.UtcNow); } - [Theory] - [BitAutoData] - [NotificationCustomize] - [CurrentContextCustomize] - public async Task PushNotificationAsync_NotificationGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.Notification, message, - new NotificationPushNotificationEquals(notification, null, installationId), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - [CurrentContextCustomize] - public async Task PushNotificationAsync_NotificationNotGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.Notification, message, - new NotificationPushNotificationEquals(notification, null, null), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - [NotificationStatusCustomize] - [CurrentContextCustomize] - public async Task PushNotificationStatusAsync_NotificationGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.NotificationStatus, message, - new NotificationPushNotificationEquals(notification, notificationStatus, installationId), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - [NotificationStatusCustomize] - [CurrentContextCustomize] - public async Task PushNotificationStatusAsync_NotificationNotGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.NotificationStatus, message, - new NotificationPushNotificationEquals(notification, notificationStatus, null), - deviceIdentifier.ToString()))); - } - [Theory] [InlineData("6a5bbe1b-cf16-49a6-965f-5c2eac56a531", null)] [InlineData(null, "b9a3fcb4-2447-45c1-aad2-24de43c88c44")] @@ -844,7 +751,7 @@ public class AzureQueuePushNotificationServiceTests // ); // } - private async Task VerifyNotificationAsync(Func test, JsonNode expectedMessage) + private async Task VerifyNotificationAsync(Func test, JsonNode expectedMessage) { var queueClient = Substitute.For(); @@ -872,7 +779,7 @@ public class AzureQueuePushNotificationServiceTests _fakeTimeProvider ); - await test(sut); + await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id)); // Hoist equality checker outside the expression so that we // can more easily place a breakpoint diff --git a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs index 68acf7ec72..a1bc2c6547 100644 --- a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs @@ -1,98 +1,8 @@ #nullable enable -using Bit.Core.Enums; -using Bit.Core.NotificationCenter.Entities; -using Bit.Core.Platform.Push; -using Bit.Core.Platform.Push.Internal; -using Bit.Core.Test.NotificationCenter.AutoFixture; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using NSubstitute; -using Xunit; namespace Bit.Core.Test.Platform.Push.Services; -[SutProviderCustomize] public class MultiServicePushNotificationServiceTests { - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_Notification_Sent( - SutProvider sutProvider, Notification notification) - { - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .PushNotificationAsync(notification); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - [NotificationStatusCustomize] - public async Task PushNotificationStatusAsync_Notification_Sent( - SutProvider sutProvider, Notification notification, - NotificationStatus notificationStatus) - { - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .PushNotificationStatusAsync(notification, notificationStatus); - } - - [Theory] - [BitAutoData([null, null])] - [BitAutoData(ClientType.All, null)] - [BitAutoData([null, "test device id"])] - [BitAutoData(ClientType.All, "test device id")] - public async Task SendPayloadToUserAsync_Message_Sent(ClientType? clientType, string? deviceId, string userId, - PushType type, object payload, string identifier, SutProvider sutProvider) - { - await sutProvider.Sut.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); - } - - [Theory] - [BitAutoData([null, null])] - [BitAutoData(ClientType.All, null)] - [BitAutoData([null, "test device id"])] - [BitAutoData(ClientType.All, "test device id")] - public async Task SendPayloadToOrganizationAsync_Message_Sent(ClientType? clientType, string? deviceId, - string organizationId, PushType type, object payload, string identifier, - SutProvider sutProvider) - { - await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, - clientType); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, clientType); - } - - [Theory] - [BitAutoData([null, null])] - [BitAutoData(ClientType.All, null)] - [BitAutoData([null, "test device id"])] - [BitAutoData(ClientType.All, "test device id")] - public async Task SendPayloadToInstallationAsync_Message_Sent(ClientType? clientType, string? deviceId, - string installationId, PushType type, object payload, string identifier, - SutProvider sutProvider) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, - clientType); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType); - } + // TODO: Can add a couple tests here } diff --git a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs index d206d96d44..92706c6ccc 100644 --- a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs @@ -19,14 +19,13 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase protected override string ExpectedClientUrl() => "https://localhost:7777/send"; - protected override IPushNotificationService CreateService() + protected override IPushEngine CreateService() { return new NotificationsApiPushNotificationService( HttpClientFactory, GlobalSettings, HttpContextAccessor, - NullLogger.Instance, - FakeTimeProvider + NullLogger.Instance ); } @@ -221,7 +220,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } @@ -236,7 +235,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } @@ -251,7 +250,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } diff --git a/test/Core.Test/Platform/Push/Services/PushTestBase.cs b/test/Core.Test/Platform/Push/Services/PushTestBase.cs index 111df7ca26..3538a68127 100644 --- a/test/Core.Test/Platform/Push/Services/PushTestBase.cs +++ b/test/Core.Test/Platform/Push/Services/PushTestBase.cs @@ -15,11 +15,28 @@ using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; using RichardSzalay.MockHttp; using Xunit; +public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService +{ + public Guid InstallationId { get; } = installationId; + + public TimeProvider TimeProvider { get; } = fakeTimeProvider; + + public ILogger Logger => NullLogger.Instance; + + public Task PushAsync(PushNotification pushNotification) where T : class + => pushEngine.PushAsync(pushNotification); + + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) + => pushEngine.PushCipherAsync(cipher, pushType, collectionIds); +} + public abstract class PushTestBase { protected static readonly string DeviceIdentifier = "test_device_identifier"; @@ -51,7 +68,7 @@ public abstract class PushTestBase FakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow); } - protected abstract IPushNotificationService CreateService(); + protected abstract IPushEngine CreateService(); protected abstract string ExpectedClientUrl(); @@ -480,7 +497,7 @@ public abstract class PushTestBase }) .Respond(HttpStatusCode.OK); - await test(CreateService()); + await test(new EngineWrapper(CreateService(), FakeTimeProvider, GlobalSettings.Installation.Id)); Assert.NotNull(actualNode); diff --git a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs index faa6b5dfa7..f95531c944 100644 --- a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs @@ -4,8 +4,8 @@ using System.Text.Json.Nodes; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -14,7 +14,6 @@ using Bit.Core.Vault.Entities; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; -using Xunit; namespace Bit.Core.Test.Platform.Push.Services; @@ -38,47 +37,19 @@ public class RelayPushNotificationServiceTests : PushTestBase GlobalSettings.Installation.IdentityUri = "https://localhost:8888"; } - protected override RelayPushNotificationService CreateService() + protected override IPushEngine CreateService() { return new RelayPushNotificationService( HttpClientFactory, _deviceRepository, GlobalSettings, HttpContextAccessor, - NullLogger.Instance, - FakeTimeProvider + NullLogger.Instance ); } protected override string ExpectedClientUrl() => "https://localhost:7777/push/send"; - [Fact] - public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new { }, null) - ); - } - - [Fact] - public async Task SendPayloadToUserAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new { }, null) - ); - } - - [Fact] - public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new { }, null) - ); - } - protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionIds) { return new JsonObject From 502ab4b6451ff1f115af209254967dd46a88e517 Mon Sep 17 00:00:00 2001 From: Brant DeBow <125889545+brant-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:09:47 -0400 Subject: [PATCH 2/8] [PM-17562] Fix flickering unit test - WebhookIntegrationHandlerTests (#5973) * [PM-17562] Fix flickering unit test - WebhookIntegrationHandlerTests * Adjust to using TimeProvider and exact time matches * Refactored RabittMqIntegrationListenerService and Tests to align on TimeProvider. Cleaned up tests that do not need to use DateTime.UtcNow --- .../RabbitMqIntegrationListenerService.cs | 7 +++++-- .../WebhookIntegrationHandler.cs | 6 ++++-- .../Utilities/ServiceCollectionExtensions.cs | 3 ++- ...rviceBusIntegrationListenerServiceTests.cs | 5 ----- ...RabbitMqIntegrationListenerServiceTests.cs | 20 ++++++++++++------- .../WebhookIntegrationHandlerTests.cs | 18 ++++++++++++----- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs index a60738d62d..db6a7f9510 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs @@ -20,6 +20,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService private readonly Lazy> _lazyChannel; private readonly IRabbitMqService _rabbitMqService; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; public RabbitMqIntegrationListenerService(IIntegrationHandler handler, string routingKey, @@ -27,7 +28,8 @@ public class RabbitMqIntegrationListenerService : BackgroundService string retryQueueName, int maxRetries, IRabbitMqService rabbitMqService, - ILogger logger) + ILogger logger, + TimeProvider timeProvider) { _handler = handler; _routingKey = routingKey; @@ -35,6 +37,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService _queueName = queueName; _rabbitMqService = rabbitMqService; _logger = logger; + _timeProvider = timeProvider; _maxRetries = maxRetries; _lazyChannel = new Lazy>(() => _rabbitMqService.CreateChannelAsync()); } @@ -74,7 +77,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService var integrationMessage = JsonSerializer.Deserialize(json); if (integrationMessage is not null && integrationMessage.DelayUntilDate.HasValue && - integrationMessage.DelayUntilDate.Value > DateTime.UtcNow) + integrationMessage.DelayUntilDate.Value > _timeProvider.GetUtcNow().UtcDateTime) { await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea); await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken); diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs index 3d76077483..6dc348310d 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs @@ -9,7 +9,9 @@ using Bit.Core.AdminConsole.Models.Data.EventIntegrations; namespace Bit.Core.Services; -public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) +public class WebhookIntegrationHandler( + IHttpClientFactory httpClientFactory, + TimeProvider timeProvider) : IntegrationHandlerBase { private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName); @@ -39,7 +41,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) if (int.TryParse(value, out var seconds)) { // Retry-after was specified in seconds. Adjust DelayUntilDate by the requested number of seconds. - result.DelayUntilDate = DateTime.UtcNow.AddSeconds(seconds); + result.DelayUntilDate = timeProvider.GetUtcNow().AddSeconds(seconds).UtcDateTime; } else if (DateTimeOffset.TryParseExact(value, "r", // "r" is the round-trip format: RFC1123 diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 871a1be038..83015354bb 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -717,7 +717,8 @@ public static class ServiceCollectionExtensions retryQueueName: integrationRetryQueueName, maxRetries: maxRetries, rabbitMqService: provider.GetRequiredService(), - logger: provider.GetRequiredService>())); + logger: provider.GetRequiredService>(), + timeProvider: provider.GetRequiredService())); return services; } diff --git a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs b/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs index f53df626d1..32a305266d 100644 --- a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs @@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = 0; var result = new IntegrationHandlerResult(false, message); @@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = _maxRetries; var result = new IntegrationHandlerResult(false, message); result.Retryable = true; @@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = 0; var result = new IntegrationHandlerResult(false, message); result.Retryable = true; - result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); _handler.HandleAsync(Arg.Any()).Returns(result); var expected = (IntegrationMessage)IntegrationMessage.FromJson(message.ToJson())!; @@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); var result = new IntegrationHandlerResult(true, message); _handler.HandleAsync(Arg.Any()).Returns(result); diff --git a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs b/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs index da0b8ec377..bb3f211afa 100644 --- a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs @@ -4,6 +4,7 @@ using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; +using Microsoft.Extensions.Time.Testing; using NSubstitute; using RabbitMQ.Client; using RabbitMQ.Client.Events; @@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests private const string _queueName = "test_queue"; private const string _retryQueueName = "test_queue_retry"; private const string _routingKey = "test_routing_key"; + private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); private readonly IIntegrationHandler _handler = Substitute.For(); private readonly IRabbitMqService _rabbitMqService = Substitute.For(); private SutProvider GetSutProvider() { - return new SutProvider() + var sutProvider = new SutProvider() .SetDependency(_handler) .SetDependency(_rabbitMqService) .SetDependency(_queueName, "queueName") .SetDependency(_retryQueueName, "retryQueueName") .SetDependency(_routingKey, "routingKey") .SetDependency(_maxRetries, "maxRetries") + .WithFakeTimeProvider() .Create(); + sutProvider.GetDependency().SetUtcNow(_now); + + return sutProvider; } [Fact] @@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = 0; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = _maxRetries; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = 0; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests ); var result = new IntegrationHandlerResult(false, message); result.Retryable = true; - result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); + result.DelayUntilDate = _now.AddMinutes(1); _handler.HandleAsync(Arg.Any()).Returns(result); var expected = IntegrationMessage.FromJson(message.ToJson()); @@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, deliveryTag: 0, @@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); + message.DelayUntilDate = _now.AddMinutes(1); var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, deliveryTag: 0, diff --git a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs index 3461b1b607..676a975b77 100644 --- a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs @@ -5,6 +5,7 @@ using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; using Bit.Test.Common.MockedHttpClient; +using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; @@ -33,6 +34,7 @@ public class WebhookIntegrationHandlerTests return new SutProvider() .SetDependency(clientFactory) + .WithFakeTimeProvider() .Create(); } @@ -62,9 +64,13 @@ public class WebhookIntegrationHandlerTests } [Theory, BitAutoData] - public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage message) + public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage message) { var sutProvider = GetSutProvider(); + var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); + var retryAfter = now.AddSeconds(60); + + sutProvider.GetDependency().SetUtcNow(now); message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); _handler.Fallback @@ -78,19 +84,21 @@ public class WebhookIntegrationHandlerTests Assert.True(result.Retryable); Assert.Equal(result.Message, message); Assert.True(result.DelayUntilDate.HasValue); - Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); + Assert.Equal(retryAfter, result.DelayUntilDate.Value); Assert.Equal("Too Many Requests", result.FailureReason); } [Theory, BitAutoData] - public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage message) + public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage message) { var sutProvider = GetSutProvider(); + var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); + var retryAfter = now.AddSeconds(60); message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); _handler.Fallback .WithStatusCode(HttpStatusCode.TooManyRequests) - .WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123 + .WithHeader("Retry-After", retryAfter.ToString("r")) .WithContent(new StringContent("testtest")); var result = await sutProvider.Sut.HandleAsync(message); @@ -99,7 +107,7 @@ public class WebhookIntegrationHandlerTests Assert.True(result.Retryable); Assert.Equal(result.Message, message); Assert.True(result.DelayUntilDate.HasValue); - Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); + Assert.Equal(retryAfter, result.DelayUntilDate.Value); Assert.Equal("Too Many Requests", result.FailureReason); } From 05d74754d2ec5fae1c683a09f033c228cd914769 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 09:29:48 -0500 Subject: [PATCH 3/8] add `PM22134SdkCipherListView` feature flag (#5980) --- src/Core/Constants.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 40a0b8a1b0..4ca9ae4702 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -206,6 +206,7 @@ public static class FeatureFlagKeys public const string EndUserNotifications = "pm-10609-end-user-notifications"; public const string PhishingDetection = "phishing-detection"; public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy"; + public const string PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view"; public static List GetAllKeys() { From 91b4ef756b8518edce9aac04913a3c24a552b6ec Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Wed, 18 Jun 2025 10:47:30 -0400 Subject: [PATCH 4/8] build(ci): remove the need to cherry pick version bumps to rc (#5977) --- .github/workflows/repository-management.yml | 125 ++++---------------- 1 file changed, 20 insertions(+), 105 deletions(-) diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 178e29212a..a59bbcfa6c 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -22,6 +22,8 @@ on: required: false type: string +permissions: {} + jobs: setup: name: Setup @@ -44,49 +46,11 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - cut_branch: - name: Cut branch - if: ${{ needs.setup.outputs.branch != 'none' }} - needs: setup - runs-on: ubuntu-24.04 - steps: - - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 - id: app-token - with: - app-id: ${{ secrets.BW_GHAPP_ID }} - private-key: ${{ secrets.BW_GHAPP_KEY }} - - - name: Check out target ref - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ inputs.target_ref }} - token: ${{ steps.app-token.outputs.token }} - - - name: Check if ${{ needs.setup.outputs.branch }} branch exists - env: - BRANCH_NAME: ${{ needs.setup.outputs.branch }} - run: | - if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then - echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: Cut branch - env: - BRANCH_NAME: ${{ needs.setup.outputs.branch }} - run: | - git switch --quiet --create $BRANCH_NAME - git push --quiet --set-upstream origin $BRANCH_NAME - - bump_version: name: Bump Version if: ${{ always() }} runs-on: ubuntu-24.04 needs: - - cut_branch - setup outputs: version: ${{ steps.set-final-version-output.outputs.version }} @@ -187,14 +151,13 @@ jobs: - name: Push changes run: git push - - cherry_pick: - name: Cherry-Pick Commit(s) + cut_branch: + name: Cut branch if: ${{ needs.setup.outputs.branch != 'none' }} - runs-on: ubuntu-24.04 needs: - - bump_version - setup + - bump_version + runs-on: ubuntu-24.04 steps: - name: Generate GH App token uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 @@ -203,78 +166,30 @@ jobs: app-id: ${{ secrets.BW_GHAPP_ID }} private-key: ${{ secrets.BW_GHAPP_KEY }} - - name: Check out main branch + - name: Check out target ref uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - fetch-depth: 0 - ref: main + ref: ${{ inputs.target_ref }} token: ${{ steps.app-token.outputs.token }} - - name: Configure Git - run: | - git config --local user.email "actions@github.com" - git config --local user.name "Github Actions" - - - name: Install xmllint - run: | - sudo apt-get update - sudo apt-get install -y libxml2-utils - - - name: Perform cherry-pick(s) + - name: Check if ${{ needs.setup.outputs.branch }} branch exists env: - CUT_BRANCH: ${{ needs.setup.outputs.branch }} + BRANCH_NAME: ${{ needs.setup.outputs.branch }} run: | - # Function for cherry-picking - cherry_pick () { - local source_branch=$1 - local destination_branch=$2 - - # Get project commit/version from source branch - git switch $source_branch - SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 Directory.Build.props) - SOURCE_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props) - - # Get project commit/version from destination branch - git switch $destination_branch - DESTINATION_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props) - - if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then - git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT - git push -u origin $destination_branch - fi - } - - # If we are cutting 'hotfix-rc': - if [[ "$CUT_BRANCH" == "hotfix-rc" ]]; then - - # If the 'rc' branch exists: - if [[ $(git ls-remote --heads origin rc) ]]; then - - # Chery-pick from 'rc' into 'hotfix-rc' - cherry_pick rc hotfix-rc - - # Cherry-pick from 'main' into 'rc' - cherry_pick main rc - - # If the 'rc' branch does not exist: - else - - # Cherry-pick from 'main' into 'hotfix-rc' - cherry_pick main hotfix-rc - - fi - - # If we are cutting 'rc': - elif [[ "$CUT_BRANCH" == "rc" ]]; then - - # Cherry-pick from 'main' into 'rc' - cherry_pick main rc - + if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then + echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY + exit 1 fi + - name: Cut branch + env: + BRANCH_NAME: ${{ needs.setup.outputs.branch }} + run: | + git switch --quiet --create $BRANCH_NAME + git push --quiet --set-upstream origin $BRANCH_NAME move_future_db_scripts: name: Move finalization database scripts - needs: cherry_pick + needs: cut_branch uses: ./.github/workflows/_move_finalization_db_scripts.yml secrets: inherit From b13c95032800b76587e43c31d89d7963c6edc0cd Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:15:38 -0400 Subject: [PATCH 5/8] [BRE-848] Adding Workflow Permissions (#5985) --- .github/workflows/enforce-labels.yml | 3 +++ .github/workflows/protect-files.yml | 3 +++ .github/workflows/stale-bot.yml | 5 +++++ .github/workflows/test-database.yml | 7 +++++++ 4 files changed, 18 insertions(+) diff --git a/.github/workflows/enforce-labels.yml b/.github/workflows/enforce-labels.yml index 11d5654937..353127c751 100644 --- a/.github/workflows/enforce-labels.yml +++ b/.github/workflows/enforce-labels.yml @@ -4,6 +4,9 @@ on: workflow_call: pull_request: types: [labeled, unlabeled, opened, reopened, synchronize] + +permissions: {} + jobs: enforce-label: if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') || contains(github.event.*.labels.*.name, 'ephemeral-environment') }} diff --git a/.github/workflows/protect-files.yml b/.github/workflows/protect-files.yml index 89d6d4c6d9..546b8344a6 100644 --- a/.github/workflows/protect-files.yml +++ b/.github/workflows/protect-files.yml @@ -16,6 +16,9 @@ jobs: changed-files: name: Check for file changes runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: write outputs: changes: ${{steps.check-changes.outputs.changes_detected}} diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 9420f71cb3..83d492645e 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -8,6 +8,11 @@ jobs: stale: name: Check for stale issues and PRs runs-on: ubuntu-22.04 + permissions: + actions: write + contents: read + issues: write + pull-requests: write steps: - name: Check uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index 26db5ea0a4..23722e2e8d 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -31,10 +31,17 @@ on: - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests - "src/**/Entities/**/*.cs" # Database entity definitions +permissions: + contents: read + jobs: test: name: Run tests runs-on: ubuntu-22.04 + permissions: + contents: read + actions: read + checks: write steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 From cdfe51f9d63214f44db4583248e770bfdb0df999 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Fri, 20 Jun 2025 14:02:48 -0400 Subject: [PATCH 6/8] [PM-22783] Add windows-desktop-autotype feature flag (#5990) --- src/Core/Constants.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 4ca9ae4702..0bd8baa85e 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -138,6 +138,7 @@ public static class FeatureFlagKeys public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill"; public const string MacOsNativeCredentialSync = "macos-native-credential-sync"; public const string InlineMenuTotp = "inline-menu-totp"; + public const string WindowsDesktopAutotype = "windows-desktop-autotype"; /* Billing Team */ public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; From d2410747d08a63812b49052d38967170055b395e Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Mon, 23 Jun 2025 12:11:32 -0400 Subject: [PATCH 7/8] [PM-22503] Fix manage cipher permission (#5972) * Added new tests to validate that the ciphers are being grouped and filtered correctly when assigned to multiple collections and changing order of grouping properties. --- .../Vault/Repositories/CipherRepository.cs | 5 +- .../Vault/Repositories/CipherRepository.cs | 11 +++- .../Repositories/CipherRepositoryTests.cs | 59 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index e0a89b1685..9c75295f3f 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -98,7 +98,10 @@ public class CipherRepository : Repository, ICipherRepository return results .GroupBy(c => c.Id) - .Select(g => g.OrderByDescending(og => og.Edit).ThenByDescending(og => og.ViewPassword).First()) + .Select(g => + g.OrderByDescending(og => og.Manage) + .ThenByDescending(og => og.Edit) + .ThenByDescending(og => og.ViewPassword).First()) .ToList(); } } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index befb835e26..a560e8e107 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -457,7 +457,7 @@ public class CipherRepository : Repository cipherDetailsView = withOrganizations ? + var cipherDetailsView = withOrganizations ? new UserCipherDetailsQuery(userId).Run(dbContext) : new CipherDetailsQuery(userId).Run(dbContext); if (!withOrganizations) @@ -485,8 +485,15 @@ public class CipherRepository : Repository c.Id) + .Select(g => g.OrderByDescending(c => c.Manage) + .ThenByDescending(c => c.Edit) + .ThenByDescending(c => c.ViewPassword) + .First()) + .ToList(); } } diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs index b7feeaa79b..288752011f 100644 --- a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs @@ -571,6 +571,65 @@ public class CipherRepositoryTests Assert.True(personalDetails.Manage, "Personal ciphers should always have Manage permission"); } + [DatabaseTheory, DatabaseData] + public async Task GetManyByUserIdAsync_WhenOneCipherIsAssignedToTwoCollectionsWithDifferentPermissions_MostPrivilegedAccessIsReturnedOnTheCipher( + ICipherRepository cipherRepository, + IUserRepository userRepository, + ICollectionCipherRepository collectionCipherRepository, + ICollectionRepository collectionRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository) + { + //Arrange + var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository); + + var cipher = await cipherRepository.CreateAsync(new Cipher + { + Type = CipherType.Login, + OrganizationId = organization.Id, + Data = "" + }); + + var managedPermissionsCollection = await collectionRepository.CreateAsync(new Collection + { + Name = "Managed", + OrganizationId = organization.Id + }); + + var unmanagedPermissionsCollection = await collectionRepository.CreateAsync(new Collection + { + Name = "Unmanaged", + OrganizationId = organization.Id + }); + await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipher.Id, organization.Id, + [managedPermissionsCollection.Id, unmanagedPermissionsCollection.Id]); + + await collectionRepository.UpdateUsersAsync(managedPermissionsCollection.Id, new List + { + new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true } + }); + + await collectionRepository.UpdateUsersAsync(unmanagedPermissionsCollection.Id, new List + { + new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false } + }); + + // Act + var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); + + // Assert + Assert.Single(ciphers); + var deletableCipher = ciphers.SingleOrDefault(x => x.Id == cipher.Id); + Assert.NotNull(deletableCipher); + Assert.True(deletableCipher.Manage); + + // Annul + await cipherRepository.DeleteAsync(cipher); + await organizationUserRepository.DeleteAsync(orgUser); + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + private async Task<(User user, Organization org, OrganizationUser orgUser)> CreateTestUserAndOrganization( IUserRepository userRepository, IOrganizationRepository organizationRepository, From 173db0a2dd8c45642bb2ecb95e89814e854300e2 Mon Sep 17 00:00:00 2001 From: Graham Walker Date: Mon, 23 Jun 2025 12:12:04 -0500 Subject: [PATCH 8/8] PM-20574 & PM-20575 Adding Risk Insight Report tables, repositories, and migrations (#5839) * PM-20574 fixing namespaces on reporting work that got moved over from tools * PM-20574 adding tables, stored procedures, and migration files * PM-20574 adding dapper and ef repos and migrations * PM-20574 changing table and repo names as requested * PM-20574 updating sql scripts to new names * PM-20574 updating sql scripts * PM-20574 updating migration script for org delete by id * PM-20574 adding mysql migration * PM-20574 updating sql migration to fix database test * PM-20574 fixing migration script * PM-20574 fixing migration script * PM-20574 fixing table scripts * PM-20574 fixing table scripts * PM-20574 fixing migration script formatting * PM-20574 fixing syntax in migration script * PM-20574 fixing file names and extensions * PM-20574 fixing sql file * PM-20574 fixing sql * PM-20574 fixing directory for entities and removing scripts from other databases * PM-20574 generating new migration scripts * PM-20574 fixed reference to a stored proc * PM-20574 adding index in scripts and missing table * PM-20574 fixing merge conflicts * PM-20574 set OUTPUT param for Id property in create and update proc * PM-20574 add CreateDate to the update proc * PM-20574 amend update proc for OrganizationApplication by adding createDate * PM-20574 formatted sql and updated as per PR comments * PM-20574 updated script to fix build error * PM-20574 fixed inconsistency in db script * PM-20574 removed revisionDate, update procedures and used views * PM-20574 removed RevisionDate from designer files * PM-20574 removed revisionDate column that was missed previously * PM-20574 added revision date back into the mix * PM-20574 updated database script to fix build error * PM-20574 fixed a procedure issue * PM-20574 fix dB build error * PM-020574 fixed additional PR comments - files cleaned up * PM-20574 updated procedure was inconsistent * Update 2025-06-13-00_OrganizationReport.sql --------- Co-authored-by: voommen-livefront --- src/Api/Dirt/Controllers/ReportsController.cs | 2 +- .../Response/MemberAccessReportModel.cs | 2 +- .../MemberCipherDetailsResponseModel.cs | 1 - .../Dirt/Entities/OrganizationApplication.cs | 20 + src/Core/Dirt/Entities/OrganizationReport.cs | 20 + .../PasswordHealthReportApplication.cs | 2 +- .../Models/Data/MemberAccessCipherDetails.cs | 2 +- .../Models/Data/MemberAccessReportDetail.cs | 0 .../Data/OrganizationMemberBaseDetail.cs | 0 .../Models/Data/RiskInsightsReportDetail.cs | 0 ...dPasswordHealthReportApplicationCommand.cs | 4 +- ...pPasswordHealthReportApplicationCommand.cs | 2 +- ...GetPasswordHealthReportApplicationQuery.cs | 4 +- ...dPasswordHealthReportApplicationCommand.cs | 2 +- ...GetPasswordHealthReportApplicationQuery.cs | 2 +- .../ReportFeatures/MemberAccessReportQuery.cs | 3 +- .../IOrganizationApplicationRepository.cs | 9 + ...IOrganizationMemberBaseDetailRepository.cs | 0 .../IOrganizationReportRepository.cs | 10 + ...sswordHealthReportApplicationRepository.cs | 4 +- .../DapperServiceCollectionExtensions.cs | 3 + .../Dirt/OrganizationApplicationRepository.cs | 35 + .../Dirt/OrganizationReportRepository.cs | 35 + ...sswordHealthReportApplicationRepository.cs | 4 +- ...ationApplicationEntityTypeConfiguration.cs | 30 + ...ganizationReportEntityTypeConfiguration.cs | 30 + .../Dirt/Models/OrganizationApplication.cs | 17 + .../Dirt/Models/OrganizationReport.cs | 17 + .../Models/PasswordHealthReportApplication.cs | 4 +- .../OrganizationApplicationRepository.cs | 29 + .../OrganizationReportRepository.cs | 30 + ...sswordHealthReportApplicationRepository.cs | 8 +- ...ityFrameworkServiceCollectionExtensions.cs | 3 + .../Repositories/DatabaseContext.cs | 3 + .../OrganizationApplication_Create.sql | 25 + .../OrganizationApplication_DeleteById.sql | 7 + .../OrganizationApplication_ReadById.sql | 9 + ...zationApplication_ReadByOrganizationId.sql | 9 + .../OrganizationApplication_Update.sql | 16 + .../OrganizationReport_Create.sql | 23 + .../OrganizationReport_DeleteById.sql | 7 + .../OrganizationReport_ReadById.sql | 9 + ...rganizationReport_ReadByOrganizationId.sql | 9 + .../Dirt/Tables/OrganizationApplication.sql | 14 + .../dbo/Dirt/Tables/OrganizationReport.sql | 18 + .../Views/OrganizationApplicationView.sql | 5 + .../dbo/Dirt/Views/OrganizationReportView.sql | 2 + .../Organization_DeleteById.sql | 14 + ...wordHealthReportApplicationCommandTests.cs | 4 +- ...wordHealthReportApplicationCommandTests.cs | 4 +- ...sswordHealthReportApplicationQueryTests.cs | 4 +- ...PasswordHealthReportApplicationFixtures.cs | 2 +- ...dHealthReportApplicationRepositoryTests.cs | 4 +- .../Infrastructure.EFIntegration.Test.csproj | 1 + .../2025-06-13-00_OrganizationReport.sql | 85 + .../2025-06-13-01_OrganizationApplication.sql | 97 + .../2025-06-13_02_UpdateOrgDeleteByIdProc.sql | 161 + ...6-13-00_OrganizationReport.sql.Designer.cs | 3252 ++++++++++++++++ ...32_2025-06-13-00_OrganizationReport.sql.cs | 95 + .../DatabaseContextModelSnapshot.cs | 80 + ...6-13-00_OrganizationReport.sql.Designer.cs | 3258 +++++++++++++++++ ...36_2025-06-13-00_OrganizationReport.sql.cs | 91 + .../DatabaseContextModelSnapshot.cs | 80 + ...6-13-00_OrganizationReport.sql.Designer.cs | 3241 ++++++++++++++++ ...39_2025-06-13-00_OrganizationReport.sql.cs | 93 + .../DatabaseContextModelSnapshot.cs | 80 + 66 files changed, 11103 insertions(+), 33 deletions(-) create mode 100644 src/Core/Dirt/Entities/OrganizationApplication.cs create mode 100644 src/Core/Dirt/Entities/OrganizationReport.cs rename src/Core/Dirt/{Reports => }/Entities/PasswordHealthReportApplication.cs (92%) rename src/Core/Dirt/{Reports => }/Models/Data/MemberAccessCipherDetails.cs (96%) rename src/Core/Dirt/{Reports => }/Models/Data/MemberAccessReportDetail.cs (100%) rename src/Core/Dirt/{Reports => }/Models/Data/OrganizationMemberBaseDetail.cs (100%) rename src/Core/Dirt/{Reports => }/Models/Data/RiskInsightsReportDetail.cs (100%) create mode 100644 src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs rename src/Core/Dirt/{Reports => }/Repositories/IOrganizationMemberBaseDetailRepository.cs (100%) create mode 100644 src/Core/Dirt/Repositories/IOrganizationReportRepository.cs rename src/Core/Dirt/{Reports => }/Repositories/IPasswordHealthReportApplicationRepository.cs (74%) create mode 100644 src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs create mode 100644 src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql create mode 100644 src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql create mode 100644 src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql create mode 100644 src/Sql/dbo/Dirt/Tables/OrganizationReport.sql create mode 100644 src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql create mode 100644 src/Sql/dbo/Dirt/Views/OrganizationReportView.sql create mode 100644 util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql create mode 100644 util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql create mode 100644 util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql create mode 100644 util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs create mode 100644 util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs create mode 100644 util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs diff --git a/src/Api/Dirt/Controllers/ReportsController.cs b/src/Api/Dirt/Controllers/ReportsController.cs index 8281bdaa98..4f2697dcc5 100644 --- a/src/Api/Dirt/Controllers/ReportsController.cs +++ b/src/Api/Dirt/Controllers/ReportsController.cs @@ -2,7 +2,7 @@ using Bit.Api.Dirt.Models.Response; using Bit.Api.Tools.Models.Response; using Bit.Core.Context; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.Models.Data; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; diff --git a/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs b/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs index b8356e5d44..38a5ed90d8 100644 --- a/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs +++ b/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Models.Data; namespace Bit.Api.Dirt.Models.Response; diff --git a/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs b/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs index e5c6235de3..886cf470db 100644 --- a/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs +++ b/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs @@ -1,5 +1,4 @@ using Bit.Core.Dirt.Reports.Models.Data; - namespace Bit.Api.Dirt.Models.Response; public class MemberCipherDetailsResponseModel diff --git a/src/Core/Dirt/Entities/OrganizationApplication.cs b/src/Core/Dirt/Entities/OrganizationApplication.cs new file mode 100644 index 0000000000..259dbd60dd --- /dev/null +++ b/src/Core/Dirt/Entities/OrganizationApplication.cs @@ -0,0 +1,20 @@ +#nullable enable + +using Bit.Core.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.Dirt.Entities; + +public class OrganizationApplication : ITableObject, IRevisable +{ + public Guid Id { get; set; } + public Guid OrganizationId { get; set; } + public string Applications { get; set; } = string.Empty; + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime RevisionDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } +} diff --git a/src/Core/Dirt/Entities/OrganizationReport.cs b/src/Core/Dirt/Entities/OrganizationReport.cs new file mode 100644 index 0000000000..0f327c5c8f --- /dev/null +++ b/src/Core/Dirt/Entities/OrganizationReport.cs @@ -0,0 +1,20 @@ +#nullable enable + +using Bit.Core.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.Dirt.Entities; + +public class OrganizationReport : ITableObject +{ + public Guid Id { get; set; } + public Guid OrganizationId { get; set; } + public DateTime Date { get; set; } + public string ReportData { get; set; } = string.Empty; + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } +} diff --git a/src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs b/src/Core/Dirt/Entities/PasswordHealthReportApplication.cs similarity index 92% rename from src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs rename to src/Core/Dirt/Entities/PasswordHealthReportApplication.cs index db605d6b74..5a72a0aab0 100644 --- a/src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs +++ b/src/Core/Dirt/Entities/PasswordHealthReportApplication.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities; using Bit.Core.Utilities; -namespace Bit.Core.Dirt.Reports.Entities; +namespace Bit.Core.Dirt.Entities; public class PasswordHealthReportApplication : ITableObject, IRevisable { diff --git a/src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs b/src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs similarity index 96% rename from src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs rename to src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs index 759337d5cf..c1949ffb24 100644 --- a/src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs +++ b/src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Dirt.Reports.Models.Data; +namespace Bit.Core.Dirt.Models.Data; public class MemberAccessDetails { diff --git a/src/Core/Dirt/Reports/Models/Data/MemberAccessReportDetail.cs b/src/Core/Dirt/Models/Data/MemberAccessReportDetail.cs similarity index 100% rename from src/Core/Dirt/Reports/Models/Data/MemberAccessReportDetail.cs rename to src/Core/Dirt/Models/Data/MemberAccessReportDetail.cs diff --git a/src/Core/Dirt/Reports/Models/Data/OrganizationMemberBaseDetail.cs b/src/Core/Dirt/Models/Data/OrganizationMemberBaseDetail.cs similarity index 100% rename from src/Core/Dirt/Reports/Models/Data/OrganizationMemberBaseDetail.cs rename to src/Core/Dirt/Models/Data/OrganizationMemberBaseDetail.cs diff --git a/src/Core/Dirt/Reports/Models/Data/RiskInsightsReportDetail.cs b/src/Core/Dirt/Models/Data/RiskInsightsReportDetail.cs similarity index 100% rename from src/Core/Dirt/Reports/Models/Data/RiskInsightsReportDetail.cs rename to src/Core/Dirt/Models/Data/RiskInsightsReportDetail.cs diff --git a/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs index f8232ffa92..159cbb5c77 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs @@ -1,7 +1,7 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Core.Repositories; diff --git a/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs index 55914dca37..b955c2c958 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs @@ -1,6 +1,6 @@ using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; namespace Bit.Core.Dirt.Reports.ReportFeatures; diff --git a/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs index d9b5e79a0c..40d86338db 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs @@ -1,6 +1,6 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; namespace Bit.Core.Dirt.Reports.ReportFeatures; diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs index 0a4aa29f2f..0fd21751de 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs index ae2f759756..f7fe80b098 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; diff --git a/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs index 7ab8acb8dc..21dbfc77a4 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs @@ -10,8 +10,7 @@ namespace Bit.Core.Dirt.Reports.ReportFeatures; public class MemberAccessReportQuery( IOrganizationMemberBaseDetailRepository organizationMemberBaseDetailRepository, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, - IApplicationCacheService applicationCacheService) - : IMemberAccessReportQuery + IApplicationCacheService applicationCacheService) : IMemberAccessReportQuery { public async Task> GetMemberAccessReportsAsync( MemberAccessReportRequest request) diff --git a/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs b/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs new file mode 100644 index 0000000000..f89e84e415 --- /dev/null +++ b/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs @@ -0,0 +1,9 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Repositories; + +namespace Bit.Core.Dirt.Repositories; + +public interface IOrganizationApplicationRepository : IRepository +{ + Task> GetByOrganizationIdAsync(Guid organizationId); +} diff --git a/src/Core/Dirt/Reports/Repositories/IOrganizationMemberBaseDetailRepository.cs b/src/Core/Dirt/Repositories/IOrganizationMemberBaseDetailRepository.cs similarity index 100% rename from src/Core/Dirt/Reports/Repositories/IOrganizationMemberBaseDetailRepository.cs rename to src/Core/Dirt/Repositories/IOrganizationMemberBaseDetailRepository.cs diff --git a/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs b/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs new file mode 100644 index 0000000000..6efb6cf23a --- /dev/null +++ b/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs @@ -0,0 +1,10 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Repositories; + +namespace Bit.Core.Dirt.Repositories; + +public interface IOrganizationReportRepository : IRepository +{ + Task> GetByOrganizationIdAsync(Guid organizationId); +} + diff --git a/src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs b/src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs similarity index 74% rename from src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs rename to src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs index 5b57932868..a67f696e44 100644 --- a/src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs +++ b/src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs @@ -1,7 +1,7 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Repositories; -namespace Bit.Core.Dirt.Reports.Repositories; +namespace Bit.Core.Dirt.Repositories; public interface IPasswordHealthReportApplicationRepository : IRepository { diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index e64eabd5bf..00728ec1a0 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Providers.Repositories; using Bit.Core.Billing.Repositories; using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; using Bit.Core.Platform.Installations; @@ -70,6 +71,8 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); if (selfHosted) diff --git a/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs b/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs new file mode 100644 index 0000000000..9f1d4d1172 --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs @@ -0,0 +1,35 @@ +using System.Data; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class OrganizationApplicationRepository : Repository, IOrganizationApplicationRepository +{ + public OrganizationApplicationRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public OrganizationApplicationRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { + } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationApplication_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } +} diff --git a/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs b/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs new file mode 100644 index 0000000000..88478c31ae --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs @@ -0,0 +1,35 @@ +using System.Data; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class OrganizationReportRepository : Repository, IOrganizationReportRepository +{ + public OrganizationReportRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public OrganizationReportRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { + } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationReport_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } +} diff --git a/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs b/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs index 2445de8a9e..266386433f 100644 --- a/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs @@ -1,6 +1,6 @@ using System.Data; -using Bit.Core.Dirt.Reports.Entities; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; using Bit.Core.Settings; using Bit.Infrastructure.Dapper.Repositories; using Dapper; diff --git a/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs new file mode 100644 index 0000000000..bf0d7d011a --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs @@ -0,0 +1,30 @@ +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Configurations; + +public class OrganizationApplicationEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(s => s.Id) + .ValueGeneratedNever(); + + builder.HasIndex(s => s.Id) + .IsClustered(true); + + builder + .HasIndex(s => s.OrganizationId) + .IsClustered(false); + + builder + .HasOne(s => s.Organization) + .WithMany() + .HasForeignKey(s => s.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + builder.ToTable(nameof(OrganizationApplication)); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs new file mode 100644 index 0000000000..99794d11a2 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs @@ -0,0 +1,30 @@ +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Configurations; + +public class OrganizationReportEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(s => s.Id) + .ValueGeneratedNever(); + + builder.HasIndex(s => s.Id) + .IsClustered(true); + + builder + .HasIndex(s => s.OrganizationId) + .IsClustered(false); + + builder + .HasOne(s => s.Organization) + .WithMany() + .HasForeignKey(s => s.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + builder.ToTable(nameof(OrganizationReport)); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs new file mode 100644 index 0000000000..9aaec0af2c --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; +public class OrganizationApplication : Core.Dirt.Entities.OrganizationApplication +{ + public virtual Organization Organization { get; set; } +} + +public class OrganizationApplicationProfile : Profile +{ + public OrganizationApplicationProfile() + { + CreateMap() + .ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs new file mode 100644 index 0000000000..a7d08e142f --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; +public class OrganizationReport : Core.Dirt.Entities.OrganizationReport +{ + public virtual Organization Organization { get; set; } +} + +public class OrganizationReportProfile : Profile +{ + public OrganizationReportProfile() + { + CreateMap() + .ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs b/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs index 222bfbeb65..bc471f0844 100644 --- a/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs @@ -3,7 +3,7 @@ using Bit.Infrastructure.EntityFramework.AdminConsole.Models; namespace Bit.Infrastructure.EntityFramework.Dirt.Models; -public class PasswordHealthReportApplication : Core.Dirt.Reports.Entities.PasswordHealthReportApplication +public class PasswordHealthReportApplication : Core.Dirt.Entities.PasswordHealthReportApplication { public virtual Organization Organization { get; set; } } @@ -12,7 +12,7 @@ public class PasswordHealthReportApplicationProfile : Profile { public PasswordHealthReportApplicationProfile() { - CreateMap() + CreateMap() .ReverseMap(); } } diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs new file mode 100644 index 0000000000..f408ed85ba --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs @@ -0,0 +1,29 @@ +using AutoMapper; +using Bit.Core.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Bit.Infrastructure.EntityFramework.Repositories; +using LinqToDB; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; + +public class OrganizationApplicationRepository : + Repository, + IOrganizationApplicationRepository +{ + public OrganizationApplicationRepository(IServiceScopeFactory serviceScopeFactory, + IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationApplications) + { } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.OrganizationApplications + .Where(p => p.OrganizationId == organizationId) + .ToListAsync(); + return Mapper.Map>(results); + } + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs new file mode 100644 index 0000000000..7c7bd56dae --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs @@ -0,0 +1,30 @@ +using AutoMapper; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using LinqToDB; +using Microsoft.Extensions.DependencyInjection; + + +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; + +public class OrganizationReportRepository : + Repository, + IOrganizationReportRepository +{ + public OrganizationReportRepository(IServiceScopeFactory serviceScopeFactory, + IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationReports) + { } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.OrganizationReports + .Where(p => p.OrganizationId == organizationId) + .ToListAsync(); + return Mapper.Map>(results); + } + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs index 604a0d87a1..296a542a8b 100644 --- a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Infrastructure.EntityFramework.Dirt.Models; using Bit.Infrastructure.EntityFramework.Repositories; using LinqToDB; @@ -8,14 +8,14 @@ using Microsoft.Extensions.DependencyInjection; namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; public class PasswordHealthReportApplicationRepository : - Repository, + Repository, IPasswordHealthReportApplicationRepository { public PasswordHealthReportApplicationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PasswordHealthReportApplications) { } - public async Task> GetByOrganizationIdAsync(Guid organizationId) + public async Task> GetByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -23,7 +23,7 @@ public class PasswordHealthReportApplicationRepository : var results = await dbContext.PasswordHealthReportApplications .Where(p => p.OrganizationId == organizationId) .ToListAsync(); - return Mapper.Map>(results); + return Mapper.Map>(results); } } } diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 616b2bc434..aab83ef7a8 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Providers.Repositories; using Bit.Core.Billing.Repositories; using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Enums; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; @@ -108,6 +109,8 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); if (selfHosted) diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index e1e29cbf41..7446abdd97 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using DP = Microsoft.AspNetCore.DataProtection; + #nullable enable namespace Bit.Infrastructure.EntityFramework.Repositories; @@ -84,6 +85,8 @@ public class DatabaseContext : DbContext public DbSet OrganizationMemberBaseDetails { get; set; } public DbSet SecurityTasks { get; set; } public DbSet OrganizationInstallations { get; set; } + public DbSet OrganizationReports { get; set; } + public DbSet OrganizationApplications { get; set; } protected override void OnModelCreating(ModelBuilder builder) { diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql new file mode 100644 index 0000000000..b2bb8593ef --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql @@ -0,0 +1,25 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationApplication] + ( + [Id], + [OrganizationId], + [Applications], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Applications, + @CreationDate, + @RevisionDate + ); diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql new file mode 100644 index 0000000000..5e00333287 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql @@ -0,0 +1,7 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationApplication] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql new file mode 100644 index 0000000000..13f9667df6 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql new file mode 100644 index 0000000000..9872c72e8d --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [OrganizationId] = @OrganizationId; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql new file mode 100644 index 0000000000..5c24a307bd --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql @@ -0,0 +1,16 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + UPDATE [dbo].[OrganizationApplication] + SET + [OrganizationId] = @OrganizationId, + [Applications] = @Applications, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql new file mode 100644 index 0000000000..d0cea4d73b --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql @@ -0,0 +1,23 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Date DATETIME2(7), + @ReportData NVARCHAR(MAX), + @CreationDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationReport]( + [Id], + [OrganizationId], + [Date], + [ReportData], + [CreationDate] + ) + VALUES ( + @Id, + @OrganizationId, + @Date, + @ReportData, + @CreationDate + ); diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql new file mode 100644 index 0000000000..fa274424df --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql @@ -0,0 +1,7 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationReport] + WHERE [Id] = @Id diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql new file mode 100644 index 0000000000..046172a4c3 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql new file mode 100644 index 0000000000..6bdcf51f70 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [OrganizationId] = @OrganizationId; diff --git a/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql b/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql new file mode 100644 index 0000000000..58c8080e23 --- /dev/null +++ b/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql @@ -0,0 +1,14 @@ +CREATE TABLE [dbo].[OrganizationApplication] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Applications] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationApplication] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationApplication_OrganizationId] + ON [dbo].[OrganizationApplication]([OrganizationId] ASC); +GO diff --git a/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql b/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql new file mode 100644 index 0000000000..563877a340 --- /dev/null +++ b/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql @@ -0,0 +1,18 @@ +CREATE TABLE [dbo].[OrganizationReport] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Date] DATETIME2 (7) NOT NULL, + [ReportData] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationReport] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationReport_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId] + ON [dbo].[OrganizationReport]([OrganizationId] ASC); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId_Date] + ON [dbo].[OrganizationReport]([OrganizationId] ASC, [Date] DESC); +GO diff --git a/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql b/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql new file mode 100644 index 0000000000..af687ceb8e --- /dev/null +++ b/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql @@ -0,0 +1,5 @@ +CREATE VIEW [dbo].[OrganizationApplicationView] +AS +SELECT + * +FROM [dbo].[OrganizationApplication]; diff --git a/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql b/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql new file mode 100644 index 0000000000..202dccd35e --- /dev/null +++ b/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql @@ -0,0 +1,2 @@ +CREATE VIEW [dbo].[OrganizationReportView] AS +SELECT * FROM [dbo].[OrganizationReport]; diff --git a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql index 2daa12209f..50ad247c1a 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql @@ -137,6 +137,20 @@ BEGIN WHERE [OrganizationId] = @Id + -- Delete Organization Application + DELETE + FROM + [dbo].[OrganizationApplication] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Report + DELETE + FROM + [dbo].[OrganizationReport] + WHERE + [OrganizationId] = @Id + DELETE FROM [dbo].[Organization] diff --git a/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs index be54bc5310..849732685c 100644 --- a/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs @@ -1,9 +1,9 @@ using AutoFixture; using Bit.Core.AdminConsole.Entities; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; diff --git a/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs index 7756995805..6265f463c7 100644 --- a/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs @@ -1,8 +1,8 @@ using AutoFixture; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs b/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs index eddfd6c1bc..8513ddb9cb 100644 --- a/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs @@ -1,7 +1,7 @@ using AutoFixture; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs index f6100fc71f..31ab99dc4a 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs @@ -1,6 +1,6 @@ using AutoFixture; using AutoFixture.Kernel; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Infrastructure.EntityFramework.Dirt.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; diff --git a/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs index d796635153..5dfbb3e942 100644 --- a/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs +++ b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs @@ -1,7 +1,7 @@ using AutoFixture; using Bit.Core.AdminConsole.Entities; -using Bit.Core.Dirt.Reports.Entities; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; using Bit.Core.Repositories; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Infrastructure.Dapper.Dirt; diff --git a/test/Infrastructure.EFIntegration.Test/Infrastructure.EFIntegration.Test.csproj b/test/Infrastructure.EFIntegration.Test/Infrastructure.EFIntegration.Test.csproj index e63d3d7419..8c2eb50c5b 100644 --- a/test/Infrastructure.EFIntegration.Test/Infrastructure.EFIntegration.Test.csproj +++ b/test/Infrastructure.EFIntegration.Test/Infrastructure.EFIntegration.Test.csproj @@ -30,6 +30,7 @@ + diff --git a/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql b/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql new file mode 100644 index 0000000000..ad256c3ea6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql @@ -0,0 +1,85 @@ +IF OBJECT_ID('dbo.OrganizationReport') IS NULL +BEGIN + CREATE TABLE [dbo].[OrganizationReport] + ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Date] DATETIME2 (7) NOT NULL, + [ReportData] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationReport] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationReport_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); + + CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId] ON [dbo].[OrganizationReport]([OrganizationId] ASC); + + CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId_Date] ON [dbo].[OrganizationReport]([OrganizationId] ASC, [Date] DESC); + +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationReportView] +AS + SELECT + * + FROM + [dbo].[OrganizationReport]; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Date DATETIME2(7), + @ReportData NVARCHAR(MAX), + @CreationDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationReport]( + [Id], + [OrganizationId], + [Date], + [ReportData], + [CreationDate] + ) + VALUES ( + @Id, + @OrganizationId, + @Date, + @ReportData, + @CreationDate + ); +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationReport] + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [OrganizationId] = @OrganizationId; +GO diff --git a/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql b/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql new file mode 100644 index 0000000000..2f2a357ad3 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql @@ -0,0 +1,97 @@ +IF OBJECT_ID('dbo.OrganizationApplication') IS NULL +BEGIN + CREATE TABLE [dbo].[OrganizationApplication] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Applications] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationApplication] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); + + CREATE NONCLUSTERED INDEX [IX_OrganizationApplication_OrganizationId] + ON [dbo].[OrganizationApplication]([OrganizationId] ASC); +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationApplicationView] AS + SELECT * FROM [dbo].[OrganizationApplication]; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationApplication] + ( + [Id], + [OrganizationId], + [Applications], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Applications, + @CreationDate, + @RevisionDate + ); +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [OrganizationId] = @OrganizationId; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [Id] = @Id; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + UPDATE [dbo].[OrganizationApplication] + SET + [OrganizationId] = @OrganizationId, + [Applications] = @Applications, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationApplication] + WHERE [Id] = @Id; +GO diff --git a/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql b/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql new file mode 100644 index 0000000000..70e6e7a2c6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql @@ -0,0 +1,161 @@ +CREATE OR ALTER PROCEDURE [dbo].[Organization_DeleteById] + @Id UNIQUEIDENTIFIER +WITH RECOMPILE +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @Id + + DECLARE @BatchSize INT = 100 + WHILE @BatchSize > 0 + BEGIN + BEGIN TRANSACTION Organization_DeleteById_Ciphers + + DELETE TOP(@BatchSize) + FROM + [dbo].[Cipher] + WHERE + [UserId] IS NULL + AND [OrganizationId] = @Id + + SET @BatchSize = @@ROWCOUNT + + COMMIT TRANSACTION Organization_DeleteById_Ciphers + END + + BEGIN TRANSACTION Organization_DeleteById + + DELETE + FROM + [dbo].[AuthRequest] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[SsoUser] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[SsoConfig] + WHERE + [OrganizationId] = @Id + + DELETE CU + FROM + [dbo].[CollectionUser] CU + INNER JOIN + [dbo].[OrganizationUser] OU ON [CU].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE AP + FROM + [dbo].[AccessPolicy] AP + INNER JOIN + [dbo].[OrganizationUser] OU ON [AP].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE GU + FROM + [dbo].[GroupUser] GU + INNER JOIN + [dbo].[OrganizationUser] OU ON [GU].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE + FROM + [dbo].[OrganizationUser] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[ProviderOrganization] + WHERE + [OrganizationId] = @Id + + EXEC [dbo].[OrganizationApiKey_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationConnection_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationSponsorship_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationDomain_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationIntegration_OrganizationDeleted] @Id + + DELETE + FROM + [dbo].[Project] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[Secret] + WHERE + [OrganizationId] = @Id + + DELETE AK + FROM + [dbo].[ApiKey] AK + INNER JOIN + [dbo].[ServiceAccount] SA ON [AK].[ServiceAccountId] = [SA].[Id] + WHERE + [SA].[OrganizationId] = @Id + + DELETE AP + FROM + [dbo].[AccessPolicy] AP + INNER JOIN + [dbo].[ServiceAccount] SA ON [AP].[GrantedServiceAccountId] = [SA].[Id] + WHERE + [SA].[OrganizationId] = @Id + + DELETE + FROM + [dbo].[ServiceAccount] + WHERE + [OrganizationId] = @Id + + -- Delete Notification Status + DELETE + NS + FROM + [dbo].[NotificationStatus] NS + INNER JOIN + [dbo].[Notification] N ON N.[Id] = NS.[NotificationId] + WHERE + N.[OrganizationId] = @Id + + -- Delete Notification + DELETE + FROM + [dbo].[Notification] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Application + DELETE + FROM + [dbo].[OrganizationApplication] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Report + DELETE + FROM + [dbo].[OrganizationReport] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[Organization] + WHERE + [Id] = @Id + COMMIT TRANSACTION Organization_DeleteById + END + GO diff --git a/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..0c8987658f --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3252 @@ +// +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("20250613215532_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .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("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" }); + + 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("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("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("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .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("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("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"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + 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("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.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.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("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.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.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.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("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/20250613215532_2025-06-13-00_OrganizationReport.sql.cs b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..13650faf47 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,95 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Applications = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false), + RevisionDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Date = table.Column(type: "datetime(6)", nullable: false), + ReportData = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index c4b41921b1..ed8f56f7e6 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -967,6 +967,64 @@ namespace Bit.MySqlMigrations.Migrations 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("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("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .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") @@ -2579,6 +2637,28 @@ namespace Bit.MySqlMigrations.Migrations 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") diff --git a/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..c8d49c17f8 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3258 @@ +// +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("20250613215536_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .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("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" }); + + 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("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("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("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .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("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("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"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + 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("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.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.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("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.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.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.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("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/20250613215536_2025-06-13-00_OrganizationReport.sql.cs b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..55fe031009 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,91 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Applications = table.Column(type: "text", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false), + RevisionDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + ReportData = table.Column(type: "text", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index c4dcce5070..aa283cf352 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -972,6 +972,64 @@ namespace Bit.PostgresMigrations.Migrations 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("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("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .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") @@ -2585,6 +2643,28 @@ namespace Bit.PostgresMigrations.Migrations 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") diff --git a/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..f6c241ff5e --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3241 @@ +// +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("20250613215539_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .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("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" }); + + 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("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("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("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .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("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("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"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + 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("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.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.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("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.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.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.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("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/20250613215539_2025-06-13-00_OrganizationReport.sql.cs b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..c2e7afdc22 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + Applications = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false), + RevisionDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + Date = table.Column(type: "TEXT", nullable: false), + ReportData = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 611e042a1f..ef9fb7cbfd 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -956,6 +956,64 @@ namespace Bit.SqliteMigrations.Migrations 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("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("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .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") @@ -2568,6 +2626,28 @@ namespace Bit.SqliteMigrations.Migrations 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")