From 3b5bb76800e33222cb7581ccb9a4c36056ccd304 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Mon, 29 Dec 2025 09:30:22 -0800 Subject: [PATCH 01/58] [PM-28747] Storage limit bypass for enforce organization ownership policy (#6759) * [PM-28747] Bypass storage limit when enforce organization data ownership policy is enabled * [PM-28747] Unit tests for storage limit enforcement * [PM-28747] Add feature flag check * [PM-28747] Simplify ignore storage limits policy enforcement * [PM-28747] Add additional test cases --- ...anizationDataOwnershipPolicyRequirement.cs | 11 ++ .../Services/Implementations/CipherService.cs | 38 ++++- .../Vault/Services/CipherServiceTests.cs | 135 ++++++++++++++++++ 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs index 28d6614dcb..c9653053ea 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs @@ -72,6 +72,17 @@ public class OrganizationDataOwnershipPolicyRequirement : IPolicyRequirement { return _policyDetails.Any(p => p.OrganizationId == organizationId); } + + /// + /// Ignore storage limits if the organization has data ownership policy enabled. + /// Allows users to seamlessly migrate their data into the organization without being blocked by storage limits. + /// Organization admins will need to manage storage after migration should overages occur. + /// + public bool IgnoreStorageLimitsOnMigration(Guid organizationId) + { + return _policyDetails.Any(p => p.OrganizationId == organizationId && + p.OrganizationUserStatus == OrganizationUserStatusType.Confirmed); + } } public record DefaultCollectionRequest(Guid OrganizationUserId, bool ShouldCreateDefaultCollection) diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index bb752b471f..797b595cbe 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -2,6 +2,7 @@ #nullable disable using System.Text.Json; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; @@ -999,20 +1000,43 @@ public class CipherService : ICipherService throw new BadRequestException("Could not find organization."); } - if (hasAttachments && !org.MaxStorageGb.HasValue) + if (!await IgnoreStorageLimitsOnMigrationAsync(sharingUserId, org)) { - throw new BadRequestException("This organization cannot use attachments."); - } + if (hasAttachments && !org.MaxStorageGb.HasValue) + { + throw new BadRequestException("This organization cannot use attachments."); + } - var storageAdjustment = attachments?.Sum(a => a.Value.Size) ?? 0; - if (org.StorageBytesRemaining() < storageAdjustment) - { - throw new BadRequestException("Not enough storage available for this organization."); + var storageAdjustment = attachments?.Sum(a => a.Value.Size) ?? 0; + if (org.StorageBytesRemaining() < storageAdjustment) + { + throw new BadRequestException("Not enough storage available for this organization."); + } } ValidateCipherLastKnownRevisionDate(cipher, lastKnownRevisionDate); } + /// + /// Checks if the storage limit for the org should be ignored due to the Organization Data Ownership Policy + /// + private async Task IgnoreStorageLimitsOnMigrationAsync(Guid userId, Organization organization) + { + if (!_featureService.IsEnabled(FeatureFlagKeys.MigrateMyVaultToMyItems)) + { + return false; + } + + if (!organization.UsePolicies) + { + return false; + } + + var requirement = await _policyRequirementQuery.GetAsync(userId); + + return requirement.IgnoreStorageLimitsOnMigration(organization.Id); + } + private async Task ValidateViewPasswordUserAsync(Cipher cipher) { if (cipher.Data == null || !cipher.OrganizationId.HasValue) diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index fc84651951..058c6f68ab 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -1190,6 +1190,7 @@ public class CipherServiceTests sutProvider.GetDependency().GetByIdAsync(organizationId) .Returns(new Organization { + UsePolicies = true, PlanType = PlanType.EnterpriseAnnually, MaxStorageGb = 100 }); @@ -1206,6 +1207,140 @@ public class CipherServiceTests Arg.Is>(arg => !arg.Except(ciphers).Any())); } + [Theory, BitAutoData] + public async Task ShareManyAsync_StorageLimitBypass_Passes(SutProvider sutProvider, + IEnumerable ciphers, Guid organizationId, List collectionIds) + { + sutProvider.GetDependency().GetByIdAsync(organizationId) + .Returns(new Organization + { + Id = organizationId, + PlanType = PlanType.EnterpriseAnnually, + UsePolicies = true, + MaxStorageGb = 3, + Storage = 3221225472 // 3 GB used, so 0 remaining + }); + ciphers.FirstOrDefault().Attachments = + "{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\"," + + "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}"; + + var cipherInfos = ciphers.Select(c => (c, + (DateTime?)c.RevisionDate)); + var sharingUserId = ciphers.First().UserId.Value; + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MigrateMyVaultToMyItems).Returns(true); + + sutProvider.GetDependency() + .GetAsync(sharingUserId) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [new PolicyDetails + { + OrganizationId = organizationId, + PolicyType = PolicyType.OrganizationDataOwnership, + OrganizationUserStatus = OrganizationUserStatusType.Confirmed, + }])); + + await sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId); + await sutProvider.GetDependency().Received(1).UpdateCiphersAsync(sharingUserId, + Arg.Is>(arg => !arg.Except(ciphers).Any())); + } + + [Theory, BitAutoData] + public async Task ShareManyAsync_StorageLimit_Enforced(SutProvider sutProvider, + IEnumerable ciphers, Guid organizationId, List collectionIds) + { + sutProvider.GetDependency().GetByIdAsync(organizationId) + .Returns(new Organization + { + Id = organizationId, + PlanType = PlanType.EnterpriseAnnually, + UsePolicies = true, + MaxStorageGb = 3, + Storage = 3221225472 // 3 GB used, so 0 remaining + }); + ciphers.FirstOrDefault().Attachments = + "{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\"," + + "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}"; + + var cipherInfos = ciphers.Select(c => (c, + (DateTime?)c.RevisionDate)); + var sharingUserId = ciphers.First().UserId.Value; + + sutProvider.GetDependency() + .GetAsync(sharingUserId) + .Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, [])); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId) + ); + Assert.Contains("Not enough storage available for this organization.", exception.Message); + await sutProvider.GetDependency().DidNotReceive().UpdateCiphersAsync(sharingUserId, + Arg.Is>(arg => !arg.Except(ciphers).Any())); + } + + [Theory, BitAutoData] + public async Task ShareManyAsync_StorageLimit_Enforced_WhenFeatureFlagDisabled(SutProvider sutProvider, + IEnumerable ciphers, Guid organizationId, List collectionIds) + { + sutProvider.GetDependency().GetByIdAsync(organizationId) + .Returns(new Organization + { + Id = organizationId, + PlanType = PlanType.EnterpriseAnnually, + UsePolicies = true, + MaxStorageGb = 3, + Storage = 3221225472 // 3 GB used, so 0 remaining + }); + ciphers.FirstOrDefault().Attachments = + "{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\"," + + "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}"; + + var cipherInfos = ciphers.Select(c => (c, + (DateTime?)c.RevisionDate)); + var sharingUserId = ciphers.First().UserId.Value; + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MigrateMyVaultToMyItems).Returns(false); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId) + ); + Assert.Contains("Not enough storage available for this organization.", exception.Message); + await sutProvider.GetDependency().DidNotReceive().UpdateCiphersAsync(sharingUserId, + Arg.Is>(arg => !arg.Except(ciphers).Any())); + } + + [Theory, BitAutoData] + public async Task ShareManyAsync_StorageLimit_Enforced_WhenUsePoliciesDisabled(SutProvider sutProvider, + IEnumerable ciphers, Guid organizationId, List collectionIds) + { + sutProvider.GetDependency().GetByIdAsync(organizationId) + .Returns(new Organization + { + Id = organizationId, + PlanType = PlanType.EnterpriseAnnually, + UsePolicies = false, + MaxStorageGb = 3, + Storage = 3221225472 // 3 GB used, so 0 remaining + }); + ciphers.FirstOrDefault().Attachments = + "{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\"," + + "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}"; + + var cipherInfos = ciphers.Select(c => (c, + (DateTime?)c.RevisionDate)); + var sharingUserId = ciphers.First().UserId.Value; + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MigrateMyVaultToMyItems).Returns(true); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId) + ); + Assert.Contains("Not enough storage available for this organization.", exception.Message); + await sutProvider.GetDependency().DidNotReceive().UpdateCiphersAsync(sharingUserId, + Arg.Is>(arg => !arg.Except(ciphers).Any())); + } + private class SaveDetailsAsyncDependencies { public CipherDetails CipherDetails { get; set; } From 34b4dc3985dae407db03437a65c2a8a1851d157a Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Mon, 29 Dec 2025 13:30:57 -0500 Subject: [PATCH 02/58] [PM-29650] retain item archive date on softdelete (#6771) --- src/Core/Vault/Services/Implementations/CipherService.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index 797b595cbe..fa2cfbb209 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -719,13 +719,6 @@ public class CipherService : ICipherService cipherDetails.DeletedDate = cipherDetails.RevisionDate = DateTime.UtcNow; - if (cipherDetails.ArchivedDate.HasValue) - { - // If the cipher was archived, clear the archived date when soft deleting - // If a user were to restore an archived cipher, it should go back to the vault not the archive vault - cipherDetails.ArchivedDate = null; - } - await _securityTaskRepository.MarkAsCompleteByCipherIds([cipherDetails.Id]); await _cipherRepository.UpsertAsync(cipherDetails); await _eventService.LogCipherEventAsync(cipherDetails, EventType.Cipher_SoftDeleted); From 9a340c0fdd1fcaf2859c52e2d1e9012ec9da8e9c Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Tue, 30 Dec 2025 07:31:26 -0600 Subject: [PATCH 03/58] Allow mobile clients to create passkeys (#6383) [PM-26177] * Allow mobile clients to create vault passkeys * Document uses for authorization policies --- .../Auth/Controllers/WebAuthnController.cs | 7 +- src/Core/Auth/Identity/Policies.cs | 96 +++++++++++++++++-- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/Api/Auth/Controllers/WebAuthnController.cs b/src/Api/Auth/Controllers/WebAuthnController.cs index 60b8621c5e..833087e99c 100644 --- a/src/Api/Auth/Controllers/WebAuthnController.cs +++ b/src/Api/Auth/Controllers/WebAuthnController.cs @@ -21,7 +21,6 @@ using Microsoft.AspNetCore.Mvc; namespace Bit.Api.Auth.Controllers; [Route("webauthn")] -[Authorize(Policies.Web)] public class WebAuthnController : Controller { private readonly IUserService _userService; @@ -62,6 +61,7 @@ public class WebAuthnController : Controller _featureService = featureService; } + [Authorize(Policies.Web)] [HttpGet("")] public async Task> Get() { @@ -71,6 +71,7 @@ public class WebAuthnController : Controller return new ListResponseModel(credentials.Select(c => new WebAuthnCredentialResponseModel(c))); } + [Authorize(Policies.Application)] [HttpPost("attestation-options")] public async Task AttestationOptions([FromBody] SecretVerificationRequestModel model) { @@ -88,6 +89,7 @@ public class WebAuthnController : Controller }; } + [Authorize(Policies.Web)] [HttpPost("assertion-options")] public async Task AssertionOptions([FromBody] SecretVerificationRequestModel model) { @@ -104,6 +106,7 @@ public class WebAuthnController : Controller }; } + [Authorize(Policies.Application)] [HttpPost("")] public async Task Post([FromBody] WebAuthnLoginCredentialCreateRequestModel model) { @@ -149,6 +152,7 @@ public class WebAuthnController : Controller } } + [Authorize(Policies.Application)] [HttpPut()] public async Task UpdateCredential([FromBody] WebAuthnLoginCredentialUpdateRequestModel model) { @@ -172,6 +176,7 @@ public class WebAuthnController : Controller await _credentialRepository.UpdateAsync(credential); } + [Authorize(Policies.Web)] [HttpPost("{id}/delete")] public async Task Delete(Guid id, [FromBody] SecretVerificationRequestModel model) { diff --git a/src/Core/Auth/Identity/Policies.cs b/src/Core/Auth/Identity/Policies.cs index b2d94b0a6e..698a890006 100644 --- a/src/Core/Auth/Identity/Policies.cs +++ b/src/Core/Auth/Identity/Policies.cs @@ -5,12 +5,94 @@ public static class Policies /// /// Policy for managing access to the Send feature. /// - public const string Send = "Send"; // [Authorize(Policy = Policies.Send)] - public const string Application = "Application"; // [Authorize(Policy = Policies.Application)] - public const string Web = "Web"; // [Authorize(Policy = Policies.Web)] - public const string Push = "Push"; // [Authorize(Policy = Policies.Push)] + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Send)] + /// + /// + /// + public const string Send = "Send"; + + /// + /// Policy to manage access to general API endpoints. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Application)] + /// + /// + /// + public const string Application = "Application"; + + /// + /// Policy to manage access to API endpoints intended for use by the Web Vault and browser extension only. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Web)] + /// + /// + /// + public const string Web = "Web"; + + /// + /// Policy to restrict access to API endpoints for the Push feature. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Push)] + /// + /// + /// + public const string Push = "Push"; + + // TODO: This is unused public const string Licensing = "Licensing"; // [Authorize(Policy = Policies.Licensing)] - public const string Organization = "Organization"; // [Authorize(Policy = Policies.Organization)] - public const string Installation = "Installation"; // [Authorize(Policy = Policies.Installation)] - public const string Secrets = "Secrets"; // [Authorize(Policy = Policies.Secrets)] + + /// + /// Policy to restrict access to API endpoints related to the Organization features. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Licensing)] + /// + /// + /// + public const string Organization = "Organization"; + + /// + /// Policy to restrict access to API endpoints related to the setting up new installations. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Installation)] + /// + /// + /// + public const string Installation = "Installation"; + + /// + /// Policy to restrict access to API endpoints for Secrets Manager features. + /// + /// + /// + /// Can be used with the Authorize attribute, for example: + /// + /// [Authorize(Policy = Policies.Secrets)] + /// + /// + /// + public const string Secrets = "Secrets"; } From 86a68ab6376783403df5be11dd03467e63d2decd Mon Sep 17 00:00:00 2001 From: Brant DeBow <125889545+brant-livefront@users.noreply.github.com> Date: Tue, 30 Dec 2025 10:59:19 -0500 Subject: [PATCH 04/58] Move all event integration code to Dirt (#6757) * Move all event integration code to Dirt * Format to fix lint --- ...zationIntegrationConfigurationController.cs | 8 ++++---- .../OrganizationIntegrationController.cs | 8 ++++---- .../Controllers/SlackIntegrationController.cs | 14 +++++++------- .../Controllers/TeamsIntegrationController.cs | 14 +++++++------- ...tionIntegrationConfigurationRequestModel.cs | 5 ++--- .../OrganizationIntegrationRequestModel.cs} | 8 ++++---- ...ionIntegrationConfigurationResponseModel.cs | 4 ++-- .../OrganizationIntegrationResponseModel.cs | 8 ++++---- .../EventIntegrations/DatadogIntegration.cs | 3 --- .../Data/EventIntegrations/SlackIntegration.cs | 3 --- .../SlackIntegrationConfiguration.cs | 3 --- .../IIntegrationConfigurationDetailsCache.cs | 14 -------------- .../Entities/OrganizationIntegration.cs | 6 +++--- .../OrganizationIntegrationConfiguration.cs | 2 +- .../Enums/IntegrationType.cs | 2 +- .../Enums/OrganizationIntegrationStatus.cs | 2 +- ...tIntegrationsServiceCollectionExtensions.cs | 18 ++++++++++-------- ...anizationIntegrationConfigurationCommand.cs | 10 +++++----- ...anizationIntegrationConfigurationCommand.cs | 6 +++--- ...ganizationIntegrationConfigurationsQuery.cs | 8 ++++---- ...anizationIntegrationConfigurationCommand.cs | 4 ++-- ...anizationIntegrationConfigurationCommand.cs | 2 +- ...ganizationIntegrationConfigurationsQuery.cs | 4 ++-- ...anizationIntegrationConfigurationCommand.cs | 4 ++-- ...anizationIntegrationConfigurationCommand.cs | 10 +++++----- .../CreateOrganizationIntegrationCommand.cs | 8 ++++---- .../DeleteOrganizationIntegrationCommand.cs | 6 +++--- .../GetOrganizationIntegrationsQuery.cs | 8 ++++---- .../ICreateOrganizationIntegrationCommand.cs | 4 ++-- .../IDeleteOrganizationIntegrationCommand.cs | 2 +- .../IGetOrganizationIntegrationsQuery.cs | 4 ++-- .../IUpdateOrganizationIntegrationCommand.cs | 4 ++-- .../UpdateOrganizationIntegrationCommand.cs | 8 ++++---- .../EventIntegrations/README.md | 0 .../EventIntegrations/DatadogIntegration.cs | 3 +++ .../DatadogIntegrationConfigurationDetails.cs | 2 +- .../DatadogListenerConfiguration.cs | 4 ++-- .../Data/EventIntegrations/HecIntegration.cs | 2 +- .../HecListenerConfiguration.cs | 4 ++-- .../IEventListenerConfiguration.cs | 2 +- .../IIntegrationListenerConfiguration.cs | 4 ++-- .../EventIntegrations/IIntegrationMessage.cs | 4 ++-- .../IntegrationFailureCategory.cs | 2 +- .../IntegrationFilterGroup.cs | 2 +- .../IntegrationFilterOperation.cs | 2 +- .../EventIntegrations/IntegrationFilterRule.cs | 2 +- .../IntegrationHandlerResult.cs | 2 +- .../EventIntegrations/IntegrationMessage.cs | 4 ++-- .../EventIntegrations/IntegrationOAuthState.cs | 4 ++-- .../IntegrationTemplateContext.cs | 2 +- .../EventIntegrations/ListenerConfiguration.cs | 2 +- ...anizationIntegrationConfigurationDetails.cs | 5 ++--- .../RepositoryListenerConfiguration.cs | 2 +- .../Data/EventIntegrations/SlackIntegration.cs | 3 +++ .../SlackIntegrationConfiguration.cs | 3 +++ .../SlackIntegrationConfigurationDetails.cs | 2 +- .../SlackListenerConfiguration.cs | 4 ++-- .../Data/EventIntegrations/TeamsIntegration.cs | 4 ++-- .../TeamsIntegrationConfigurationDetails.cs | 2 +- .../TeamsListenerConfiguration.cs | 4 ++-- .../EventIntegrations/WebhookIntegration.cs | 2 +- .../WebhookIntegrationConfiguration.cs | 2 +- .../WebhookIntegrationConfigurationDetails.cs | 2 +- .../WebhookListenerConfiguration.cs | 4 ++-- .../Models/Data}/Slack/SlackApiResponse.cs | 2 +- .../Models/Data}/Teams/TeamsApiResponse.cs | 2 +- .../Data}/Teams/TeamsBotCredentialProvider.cs | 2 +- ...zationIntegrationConfigurationRepository.cs | 8 +++++--- .../IOrganizationIntegrationRepository.cs | 5 +++-- .../Services/IAzureServiceBusService.cs | 4 ++-- .../Services/IEventIntegrationPublisher.cs | 4 ++-- .../Services/IEventMessageHandler.cs | 2 +- .../Services/IIntegrationFilterService.cs | 4 ++-- .../Services/IIntegrationHandler.cs | 4 ++-- ...izationIntegrationConfigurationValidator.cs | 6 +++--- .../Services/IRabbitMqService.cs | 4 ++-- .../Services/ISlackService.cs | 5 +++-- .../Services/ITeamsService.cs | 5 +++-- .../AzureServiceBusEventListenerService.cs | 6 +++--- ...zureServiceBusIntegrationListenerService.cs | 6 +++--- .../Implementations}/AzureServiceBusService.cs | 6 +++--- .../AzureTableStorageEventHandler.cs | 7 +++---- .../DatadogIntegrationHandler.cs | 4 ++-- .../EventIntegrationEventWriteService.cs | 3 ++- .../EventIntegrationHandler.cs | 8 ++++---- .../EventLoggingListenerService.cs | 6 ++---- .../Implementations}/EventRepositoryHandler.cs | 3 ++- .../IntegrationFilterFactory.cs | 2 +- .../IntegrationFilterService.cs | 4 ++-- ...izationIntegrationConfigurationValidator.cs | 8 ++++---- .../RabbitMqEventListenerService.cs | 6 +++--- .../RabbitMqIntegrationListenerService.cs | 6 +++--- .../Implementations}/RabbitMqService.cs | 6 +++--- .../SlackIntegrationHandler.cs | 4 ++-- .../Services/Implementations}/SlackService.cs | 4 ++-- .../TeamsIntegrationHandler.cs | 4 ++-- .../Services/Implementations}/TeamsService.cs | 10 +++++----- .../WebhookIntegrationHandler.cs | 4 ++-- .../NoopImplementations/NoopSlackService.cs | 5 ++--- .../NoopImplementations/NoopTeamsService.cs | 5 ++--- .../EventIntegrationsCacheConstants.cs | 3 ++- .../DapperServiceCollectionExtensions.cs | 1 + ...zationIntegrationConfigurationRepository.cs | 9 +++++---- .../OrganizationIntegrationRepository.cs | 7 ++++--- ...tionConfigurationEntityTypeConfiguration.cs | 2 +- ...zationIntegrationEntityTypeConfiguration.cs | 2 +- .../Models/OrganizationIntegration.cs | 16 ---------------- .../OrganizationIntegrationConfiguration.cs | 16 ---------------- .../Dirt/Models/OrganizationIntegration.cs | 17 +++++++++++++++++ .../OrganizationIntegrationConfiguration.cs | 16 ++++++++++++++++ ...zationIntegrationConfigurationRepository.cs | 16 ++++++++-------- .../OrganizationIntegrationRepository.cs | 14 +++++++------- ...ntTypeOrganizationIdIntegrationTypeQuery.cs | 9 ++++++--- ...grationConfigurationDetailsReadManyQuery.cs | 8 ++++---- ...ReadManyByOrganizationIntegrationIdQuery.cs | 4 ++-- ...dByTeamsConfigurationTenantIdTeamIdQuery.cs | 6 +++--- ...IntegrationReadManyByOrganizationIdQuery.cs | 4 ++-- .../OrganizationIntegrationControllerTests.cs | 14 +++++++------- ...IntegrationsConfigurationControllerTests.cs | 12 ++++++------ .../SlackIntegrationControllerTests.cs | 14 +++++++------- .../TeamsIntegrationControllerTests.cs | 16 ++++++++-------- ...OrganizationIntegrationRequestModelTests.cs | 10 +++++----- ...rganizationIntegrationResponseModelTests.cs | 12 ++++++------ .../Services/IntegrationTypeTests.cs | 2 +- ...egrationServiceCollectionExtensionsTests.cs | 17 ++++++++++------- ...tionIntegrationConfigurationCommandTests.cs | 11 ++++++----- ...tionIntegrationConfigurationCommandTests.cs | 9 +++++---- ...ationIntegrationConfigurationsQueryTests.cs | 8 ++++---- ...tionIntegrationConfigurationCommandTests.cs | 11 ++++++----- ...reateOrganizationIntegrationCommandTests.cs | 10 +++++----- ...eleteOrganizationIntegrationCommandTests.cs | 10 +++++----- .../GetOrganizationIntegrationsQueryTests.cs | 8 ++++---- ...pdateOrganizationIntegrationCommandTests.cs | 10 +++++----- .../IntegrationHandlerResultTests.cs | 4 ++-- .../IntegrationMessageTests.cs | 6 +++--- .../IntegrationOAuthStateTests.cs | 6 +++--- .../IntegrationTemplateContextTests.cs | 4 ++-- ...tionIntegrationConfigurationDetailsTests.cs | 4 ++-- .../TestListenerConfiguration.cs | 5 +++-- .../Teams/TeamsBotCredentialProviderTests.cs | 4 ++-- ...AzureServiceBusEventListenerServiceTests.cs | 7 ++++--- ...erviceBusIntegrationListenerServiceTests.cs | 8 +++++--- .../Services/DatadogIntegrationHandlerTests.cs | 6 +++--- .../EventIntegrationEventWriteServiceTests.cs | 5 +++-- .../Services/EventIntegrationHandlerTests.cs | 11 ++++++----- .../Services/EventRepositoryHandlerTests.cs | 5 +++-- .../Services/IntegrationFilterFactoryTests.cs | 6 +++--- .../Services/IntegrationFilterServiceTests.cs | 6 +++--- .../Services/IntegrationHandlerTests.cs | 8 ++++---- ...onIntegrationConfigurationValidatorTests.cs | 10 +++++----- .../RabbitMqEventListenerServiceTests.cs | 7 ++++--- .../RabbitMqIntegrationListenerServiceTests.cs | 8 +++++--- .../Services/SlackIntegrationHandlerTests.cs | 9 +++++---- .../Services/SlackServiceTests.cs | 4 ++-- .../Services/TeamsIntegrationHandlerTests.cs | 7 ++++--- .../Services/TeamsServiceTests.cs | 12 ++++++------ .../Services/WebhookIntegrationHandlerTests.cs | 6 +++--- .../EventIntegrationsCacheConstantsTests.cs | 3 ++- 158 files changed, 487 insertions(+), 472 deletions(-) rename src/Api/{AdminConsole => Dirt}/Controllers/OrganizationIntegrationConfigurationController.cs (92%) rename src/Api/{AdminConsole => Dirt}/Controllers/OrganizationIntegrationController.cs (91%) rename src/Api/{AdminConsole => Dirt}/Controllers/SlackIntegrationController.cs (94%) rename src/Api/{AdminConsole => Dirt}/Controllers/TeamsIntegrationController.cs (94%) rename src/Api/{AdminConsole/Models/Request/Organizations => Dirt/Models/Request}/OrganizationIntegrationConfigurationRequestModel.cs (86%) rename src/Api/{AdminConsole/Models/Request/Organizations/OrgnizationIntegrationRequestModel.cs => Dirt/Models/Request/OrganizationIntegrationRequestModel.cs} (94%) rename src/Api/{AdminConsole/Models/Response/Organizations => Dirt/Models/Response}/OrganizationIntegrationConfigurationResponseModel.cs (90%) rename src/Api/{AdminConsole/Models/Response/Organizations => Dirt/Models/Response}/OrganizationIntegrationResponseModel.cs (93%) delete mode 100644 src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegration.cs delete mode 100644 src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs delete mode 100644 src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs delete mode 100644 src/Core/AdminConsole/Services/IIntegrationConfigurationDetailsCache.cs rename src/Core/{AdminConsole => Dirt}/Entities/OrganizationIntegration.cs (83%) rename src/Core/{AdminConsole => Dirt}/Entities/OrganizationIntegrationConfiguration.cs (93%) rename src/Core/{AdminConsole => Dirt}/Enums/IntegrationType.cs (96%) rename src/Core/{AdminConsole => Dirt}/Enums/OrganizationIntegrationStatus.cs (66%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs (98%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs (89%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs (90%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs (78%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs (88%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs (89%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs (85%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs (90%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs (92%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs (85%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs (85%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs (68%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs (83%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs (87%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs (80%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs (87%) rename src/Core/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs (86%) rename src/Core/{AdminConsole/Services/Implementations => Dirt}/EventIntegrations/README.md (100%) create mode 100644 src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegration.cs rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs (54%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs (91%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/HecIntegration.cs (58%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/HecListenerConfiguration.cs (91%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IEventListenerConfiguration.cs (80%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs (86%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IIntegrationMessage.cs (77%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationFailureCategory.cs (93%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationFilterGroup.cs (76%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationFilterOperation.cs (61%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationFilterRule.cs (76%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationHandlerResult.cs (97%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationMessage.cs (93%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationOAuthState.cs (95%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationTemplateContext.cs (97%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/ListenerConfiguration.cs (94%) rename src/Core/{AdminConsole/Models/Data/Organizations => Dirt/Models/Data/EventIntegrations}/OrganizationIntegrationConfigurationDetails.cs (95%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs (87%) create mode 100644 src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegration.cs create mode 100644 src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs (56%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/SlackListenerConfiguration.cs (91%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/TeamsIntegration.cs (71%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs (56%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs (91%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/WebhookIntegration.cs (57%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs (60%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs (62%) rename src/Core/{AdminConsole => Dirt}/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs (91%) rename src/Core/{AdminConsole/Models => Dirt/Models/Data}/Slack/SlackApiResponse.cs (97%) rename src/Core/{AdminConsole/Models => Dirt/Models/Data}/Teams/TeamsApiResponse.cs (97%) rename src/Core/{AdminConsole/Models => Dirt/Models/Data}/Teams/TeamsBotCredentialProvider.cs (94%) rename src/Core/{AdminConsole => Dirt}/Repositories/IOrganizationIntegrationConfigurationRepository.cs (88%) rename src/Core/{AdminConsole => Dirt}/Repositories/IOrganizationIntegrationRepository.cs (74%) rename src/Core/{AdminConsole => Dirt}/Services/IAzureServiceBusService.cs (77%) rename src/Core/{AdminConsole => Dirt}/Services/IEventIntegrationPublisher.cs (67%) rename src/Core/{AdminConsole => Dirt}/Services/IEventMessageHandler.cs (85%) rename src/Core/{AdminConsole => Dirt}/Services/IIntegrationFilterService.cs (67%) rename src/Core/{AdminConsole => Dirt}/Services/IIntegrationHandler.cs (98%) rename src/Core/{AdminConsole => Dirt}/Services/IOrganizationIntegrationConfigurationValidator.cs (86%) rename src/Core/{AdminConsole => Dirt}/Services/IRabbitMqService.cs (89%) rename src/Core/{AdminConsole => Dirt}/Services/ISlackService.cs (97%) rename src/Core/{AdminConsole => Dirt}/Services/ITeamsService.cs (95%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/AzureServiceBusEventListenerService.cs (89%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/AzureServiceBusIntegrationListenerService.cs (94%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/AzureServiceBusService.cs (94%) rename src/Core/{AdminConsole => Dirt}/Services/Implementations/AzureTableStorageEventHandler.cs (84%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/DatadogIntegrationHandler.cs (90%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/EventIntegrationHandler.cs (97%) rename src/Core/{AdminConsole/Services => Dirt/Services/Implementations}/EventLoggingListenerService.cs (97%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/EventRepositoryHandler.cs (87%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/IntegrationFilterFactory.cs (97%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/IntegrationFilterService.cs (97%) rename src/Core/{AdminConsole/Services => Dirt/Services/Implementations}/OrganizationIntegrationConfigurationValidator.cs (92%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/RabbitMqEventListenerService.cs (91%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/RabbitMqIntegrationListenerService.cs (96%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/RabbitMqService.cs (98%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/SlackIntegrationHandler.cs (96%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/SlackService.cs (98%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/TeamsIntegrationHandler.cs (94%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/TeamsService.cs (96%) rename src/Core/{AdminConsole/Services/Implementations/EventIntegrations => Dirt/Services/Implementations}/WebhookIntegrationHandler.cs (92%) rename src/Core/{AdminConsole => Dirt}/Services/NoopImplementations/NoopSlackService.cs (88%) rename src/Core/{AdminConsole => Dirt}/Services/NoopImplementations/NoopTeamsService.cs (83%) rename src/Infrastructure.Dapper/{AdminConsole => Dirt}/Repositories/OrganizationIntegrationConfigurationRepository.cs (93%) rename src/Infrastructure.Dapper/{AdminConsole => Dirt}/Repositories/OrganizationIntegrationRepository.cs (90%) delete mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegration.cs delete mode 100644 src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegrationConfiguration.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegration.cs create mode 100644 src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegrationConfiguration.cs rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/OrganizationIntegrationConfigurationRepository.cs (75%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/OrganizationIntegrationRepository.cs (67%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs (82%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs (82%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs (91%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs (89%) rename src/Infrastructure.EntityFramework/{AdminConsole => Dirt}/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs (88%) rename test/Api.Test/{AdminConsole => Dirt}/Controllers/OrganizationIntegrationControllerTests.cs (95%) rename test/Api.Test/{AdminConsole => Dirt}/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs (96%) rename test/Api.Test/{AdminConsole => Dirt}/Controllers/SlackIntegrationControllerTests.cs (98%) rename test/Api.Test/{AdminConsole => Dirt}/Controllers/TeamsIntegrationControllerTests.cs (98%) rename test/Api.Test/{AdminConsole/Models/Request/Organizations => Dirt/Models/Request}/OrganizationIntegrationRequestModelTests.cs (97%) rename test/Api.Test/{AdminConsole/Models/Response/Organizations => Dirt/Models/Response}/OrganizationIntegrationResponseModelTests.cs (94%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs (98%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs (96%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs (94%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs (98%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs (93%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs (92%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs (86%) rename test/Core.Test/{AdminConsole => Dirt}/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs (95%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs (96%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationMessageTests.cs (96%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs (94%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs (97%) rename test/Core.Test/{AdminConsole/Models/Data/Organizations => Dirt/Models/Data/EventIntegrations}/OrganizationIntegrationConfigurationDetailsTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/EventIntegrations/TestListenerConfiguration.cs (86%) rename test/Core.Test/{AdminConsole => Dirt}/Models/Data/Teams/TeamsBotCredentialProviderTests.cs (95%) rename test/Core.Test/{AdminConsole => Dirt}/Services/AzureServiceBusEventListenerServiceTests.cs (96%) rename test/Core.Test/{AdminConsole => Dirt}/Services/AzureServiceBusIntegrationListenerServiceTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/DatadogIntegrationHandlerTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/EventIntegrationEventWriteServiceTests.cs (95%) rename test/Core.Test/{AdminConsole => Dirt}/Services/EventIntegrationHandlerTests.cs (99%) rename test/Core.Test/{AdminConsole => Dirt}/Services/EventRepositoryHandlerTests.cs (90%) rename test/Core.Test/{AdminConsole => Dirt}/Services/IntegrationFilterFactoryTests.cs (91%) rename test/Core.Test/{AdminConsole => Dirt}/Services/IntegrationFilterServiceTests.cs (99%) rename test/Core.Test/{AdminConsole => Dirt}/Services/IntegrationHandlerTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/OrganizationIntegrationConfigurationValidatorTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/RabbitMqEventListenerServiceTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/RabbitMqIntegrationListenerServiceTests.cs (98%) rename test/Core.Test/{AdminConsole => Dirt}/Services/SlackIntegrationHandlerTests.cs (96%) rename test/Core.Test/{AdminConsole => Dirt}/Services/SlackServiceTests.cs (99%) rename test/Core.Test/{AdminConsole => Dirt}/Services/TeamsIntegrationHandlerTests.cs (98%) rename test/Core.Test/{AdminConsole => Dirt}/Services/TeamsServiceTests.cs (97%) rename test/Core.Test/{AdminConsole => Dirt}/Services/WebhookIntegrationHandlerTests.cs (98%) diff --git a/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs b/src/Api/Dirt/Controllers/OrganizationIntegrationConfigurationController.cs similarity index 92% rename from src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs rename to src/Api/Dirt/Controllers/OrganizationIntegrationConfigurationController.cs index f172a23529..4296aa3edd 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationIntegrationConfigurationController.cs +++ b/src/Api/Dirt/Controllers/OrganizationIntegrationConfigurationController.cs @@ -1,12 +1,12 @@ -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Api.Dirt.Models.Request; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; using Bit.Core.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.AdminConsole.Controllers; +namespace Bit.Api.Dirt.Controllers; [Route("organizations/{organizationId:guid}/integrations/{integrationId:guid}/configurations")] [Authorize("Application")] diff --git a/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs b/src/Api/Dirt/Controllers/OrganizationIntegrationController.cs similarity index 91% rename from src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs rename to src/Api/Dirt/Controllers/OrganizationIntegrationController.cs index b82fe3dfa8..960db648c2 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationIntegrationController.cs +++ b/src/Api/Dirt/Controllers/OrganizationIntegrationController.cs @@ -1,12 +1,12 @@ -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Api.Dirt.Models.Request; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; using Bit.Core.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.AdminConsole.Controllers; +namespace Bit.Api.Dirt.Controllers; [Route("organizations/{organizationId:guid}/integrations")] [Authorize("Application")] diff --git a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs b/src/Api/Dirt/Controllers/SlackIntegrationController.cs similarity index 94% rename from src/Api/AdminConsole/Controllers/SlackIntegrationController.cs rename to src/Api/Dirt/Controllers/SlackIntegrationController.cs index 7b53f73f81..e98ed0d3fa 100644 --- a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs +++ b/src/Api/Dirt/Controllers/SlackIntegrationController.cs @@ -1,16 +1,16 @@ using System.Text.Json; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.AdminConsole.Controllers; +namespace Bit.Api.Dirt.Controllers; [Route("organizations")] [Authorize("Application")] diff --git a/src/Api/AdminConsole/Controllers/TeamsIntegrationController.cs b/src/Api/Dirt/Controllers/TeamsIntegrationController.cs similarity index 94% rename from src/Api/AdminConsole/Controllers/TeamsIntegrationController.cs rename to src/Api/Dirt/Controllers/TeamsIntegrationController.cs index 36d107bbcc..b2bd55017c 100644 --- a/src/Api/AdminConsole/Controllers/TeamsIntegrationController.cs +++ b/src/Api/Dirt/Controllers/TeamsIntegrationController.cs @@ -1,18 +1,18 @@ using System.Text.Json; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; -namespace Bit.Api.AdminConsole.Controllers; +namespace Bit.Api.Dirt.Controllers; [Route("organizations")] [Authorize("Application")] diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs b/src/Api/Dirt/Models/Request/OrganizationIntegrationConfigurationRequestModel.cs similarity index 86% rename from src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs rename to src/Api/Dirt/Models/Request/OrganizationIntegrationConfigurationRequestModel.cs index 9341392d68..e918bea2d6 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs +++ b/src/Api/Dirt/Models/Request/OrganizationIntegrationConfigurationRequestModel.cs @@ -1,8 +1,7 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Enums; - -namespace Bit.Api.AdminConsole.Models.Request.Organizations; +namespace Bit.Api.Dirt.Models.Request; public class OrganizationIntegrationConfigurationRequestModel { diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrgnizationIntegrationRequestModel.cs b/src/Api/Dirt/Models/Request/OrganizationIntegrationRequestModel.cs similarity index 94% rename from src/Api/AdminConsole/Models/Request/Organizations/OrgnizationIntegrationRequestModel.cs rename to src/Api/Dirt/Models/Request/OrganizationIntegrationRequestModel.cs index 668afe70bf..259671bd66 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrgnizationIntegrationRequestModel.cs +++ b/src/Api/Dirt/Models/Request/OrganizationIntegrationRequestModel.cs @@ -1,10 +1,10 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Api.AdminConsole.Models.Request.Organizations; +namespace Bit.Api.Dirt.Models.Request; public class OrganizationIntegrationRequestModel : IValidatableObject { diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs b/src/Api/Dirt/Models/Response/OrganizationIntegrationConfigurationResponseModel.cs similarity index 90% rename from src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs rename to src/Api/Dirt/Models/Response/OrganizationIntegrationConfigurationResponseModel.cs index d070375d88..62a3aea405 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs +++ b/src/Api/Dirt/Models/Response/OrganizationIntegrationConfigurationResponseModel.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; -namespace Bit.Api.AdminConsole.Models.Response.Organizations; +namespace Bit.Api.Dirt.Models.Response; public class OrganizationIntegrationConfigurationResponseModel : ResponseModel { diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModel.cs b/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs similarity index 93% rename from src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModel.cs rename to src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs index 0c31e07bef..60e885fe82 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModel.cs +++ b/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs @@ -1,10 +1,10 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Models.Api; -namespace Bit.Api.AdminConsole.Models.Response.Organizations; +namespace Bit.Api.Dirt.Models.Response; public class OrganizationIntegrationResponseModel : ResponseModel { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegration.cs deleted file mode 100644 index 8785a74896..0000000000 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegration.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; - -public record DatadogIntegration(string ApiKey, Uri Uri); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs deleted file mode 100644 index dc2733c889..0000000000 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; - -public record SlackIntegration(string Token); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs deleted file mode 100644 index 5b4fae0c76..0000000000 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; - -public record SlackIntegrationConfiguration(string ChannelId); diff --git a/src/Core/AdminConsole/Services/IIntegrationConfigurationDetailsCache.cs b/src/Core/AdminConsole/Services/IIntegrationConfigurationDetailsCache.cs deleted file mode 100644 index ad27429112..0000000000 --- a/src/Core/AdminConsole/Services/IIntegrationConfigurationDetailsCache.cs +++ /dev/null @@ -1,14 +0,0 @@ -#nullable enable - -using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; - -namespace Bit.Core.Services; - -public interface IIntegrationConfigurationDetailsCache -{ - List GetConfigurationDetails( - Guid organizationId, - IntegrationType integrationType, - EventType eventType); -} diff --git a/src/Core/AdminConsole/Entities/OrganizationIntegration.cs b/src/Core/Dirt/Entities/OrganizationIntegration.cs similarity index 83% rename from src/Core/AdminConsole/Entities/OrganizationIntegration.cs rename to src/Core/Dirt/Entities/OrganizationIntegration.cs index f1c96c8b98..42b4e89e27 100644 --- a/src/Core/AdminConsole/Entities/OrganizationIntegration.cs +++ b/src/Core/Dirt/Entities/OrganizationIntegration.cs @@ -1,8 +1,8 @@ -using Bit.Core.Entities; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Entities; using Bit.Core.Utilities; -namespace Bit.Core.AdminConsole.Entities; +namespace Bit.Core.Dirt.Entities; public class OrganizationIntegration : ITableObject { diff --git a/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs b/src/Core/Dirt/Entities/OrganizationIntegrationConfiguration.cs similarity index 93% rename from src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs rename to src/Core/Dirt/Entities/OrganizationIntegrationConfiguration.cs index a9ce676062..2b8dbf9220 100644 --- a/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs +++ b/src/Core/Dirt/Entities/OrganizationIntegrationConfiguration.cs @@ -2,7 +2,7 @@ using Bit.Core.Enums; using Bit.Core.Utilities; -namespace Bit.Core.AdminConsole.Entities; +namespace Bit.Core.Dirt.Entities; public class OrganizationIntegrationConfiguration : ITableObject { diff --git a/src/Core/AdminConsole/Enums/IntegrationType.cs b/src/Core/Dirt/Enums/IntegrationType.cs similarity index 96% rename from src/Core/AdminConsole/Enums/IntegrationType.cs rename to src/Core/Dirt/Enums/IntegrationType.cs index 84e4de94e9..767f2feb06 100644 --- a/src/Core/AdminConsole/Enums/IntegrationType.cs +++ b/src/Core/Dirt/Enums/IntegrationType.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Enums; +namespace Bit.Core.Dirt.Enums; public enum IntegrationType : int { diff --git a/src/Core/AdminConsole/Enums/OrganizationIntegrationStatus.cs b/src/Core/Dirt/Enums/OrganizationIntegrationStatus.cs similarity index 66% rename from src/Core/AdminConsole/Enums/OrganizationIntegrationStatus.cs rename to src/Core/Dirt/Enums/OrganizationIntegrationStatus.cs index 78a7bc6d63..aad0530971 100644 --- a/src/Core/AdminConsole/Enums/OrganizationIntegrationStatus.cs +++ b/src/Core/Dirt/Enums/OrganizationIntegrationStatus.cs @@ -1,4 +1,4 @@ -namespace Bit.Api.AdminConsole.Models.Response.Organizations; +namespace Bit.Core.Dirt.Enums; public enum OrganizationIntegrationStatus : int { diff --git a/src/Core/AdminConsole/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs b/src/Core/Dirt/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs similarity index 98% rename from src/Core/AdminConsole/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs rename to src/Core/Dirt/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs index ebeef44484..b03a68cfa6 100644 --- a/src/Core/AdminConsole/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs +++ b/src/Core/Dirt/EventIntegrations/EventIntegrationsServiceCollectionExtensions.cs @@ -1,13 +1,15 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.AdminConsole.Models.Teams; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.AdminConsole.Services; -using Bit.Core.AdminConsole.Services.NoopImplementations; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Teams; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Dirt.Services.NoopImplementations; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs similarity index 89% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs index cb3ce8b9ea..478b43bb7e 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommand.cs @@ -1,13 +1,13 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; -using Bit.Core.AdminConsole.Services; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; /// /// Command implementation for creating organization integration configurations with validation and cache invalidation support. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs similarity index 90% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs index 78768fd0d4..d6369f1b1b 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommand.cs @@ -1,11 +1,11 @@ -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; /// /// Command implementation for deleting organization integration configurations with cache invalidation support. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs similarity index 78% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs index a2078c3c98..6dfe2949a4 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQuery.cs @@ -1,9 +1,9 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; /// /// Query implementation for retrieving organization integration configurations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs similarity index 88% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs index 140cc79d1a..629a1ee8ed 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/ICreateOrganizationIntegrationConfigurationCommand.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; /// /// Command interface for creating organization integration configurations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs similarity index 89% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs index 3970676d40..d6866443c2 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IDeleteOrganizationIntegrationConfigurationCommand.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; /// /// Command interface for deleting organization integration configurations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs similarity index 85% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs index 2bf806c458..a6635cb3be 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IGetOrganizationIntegrationConfigurationsQuery.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; /// /// Query interface for retrieving organization integration configurations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs similarity index 90% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs index 3e60a0af07..3ed680b808 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/Interfaces/IUpdateOrganizationIntegrationConfigurationCommand.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; /// /// Command interface for updating organization integration configurations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs similarity index 92% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs index f619e2ddf2..69c28f3e7e 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommand.cs @@ -1,13 +1,13 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; -using Bit.Core.AdminConsole.Services; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; /// /// Command implementation for updating organization integration configurations with validation and cache invalidation support. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs similarity index 85% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs index 376451977c..4423c103f9 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommand.cs @@ -1,12 +1,12 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; /// /// Command implementation for creating organization integrations with cache invalidation support. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs similarity index 85% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs index 614693cd82..dc1e7fb1dc 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommand.cs @@ -1,11 +1,11 @@ -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; /// /// Command implementation for deleting organization integrations with cache invalidation support. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs similarity index 68% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs index f7bbaadb4a..807f0b0b59 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQuery.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; -using Bit.Core.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Repositories; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; /// /// Query implementation for retrieving organization integrations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs similarity index 83% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs index e7b79eab13..0b06d79bdb 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/ICreateOrganizationIntegrationCommand.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; /// /// Command interface for creating an OrganizationIntegration. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs similarity index 87% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs index be22b4e482..8640f03ec8 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IDeleteOrganizationIntegrationCommand.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; /// /// Command interface for deleting organization integrations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs similarity index 80% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs index 8cdea7f301..1f378abe9b 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IGetOrganizationIntegrationsQuery.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; /// /// Query interface for retrieving organization integrations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs similarity index 87% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs index f40086600d..ddba2bd233 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/Interfaces/IUpdateOrganizationIntegrationCommand.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; /// /// Command interface for updating organization integrations. diff --git a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs similarity index 86% rename from src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs rename to src/Core/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs index 12a8620926..77a3448276 100644 --- a/src/Core/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs +++ b/src/Core/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommand.cs @@ -1,12 +1,12 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; /// /// Command implementation for updating organization integrations with cache invalidation support. diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/README.md b/src/Core/Dirt/EventIntegrations/README.md similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/README.md rename to src/Core/Dirt/EventIntegrations/README.md diff --git a/src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegration.cs new file mode 100644 index 0000000000..69a4deb66b --- /dev/null +++ b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegration.cs @@ -0,0 +1,3 @@ +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; + +public record DatadogIntegration(string ApiKey, Uri Uri); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs similarity index 54% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs index 07aafa4bd8..ed91c3828b 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogIntegrationConfigurationDetails.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record DatadogIntegrationConfigurationDetails(string ApiKey, Uri Uri); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs similarity index 91% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs index 1c74826791..ce35e29927 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/DatadogListenerConfiguration.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class DatadogListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/HecIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/HecIntegration.cs similarity index 58% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/HecIntegration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/HecIntegration.cs index 33ae5dadbe..df943e0bfc 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/HecIntegration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/HecIntegration.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record HecIntegration(Uri Uri, string Scheme, string Token, string? Service = null); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/HecListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/HecListenerConfiguration.cs similarity index 91% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/HecListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/HecListenerConfiguration.cs index 37a0d68beb..5ceb42be64 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/HecListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/HecListenerConfiguration.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class HecListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IEventListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IEventListenerConfiguration.cs similarity index 80% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IEventListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IEventListenerConfiguration.cs index 7df1459941..206dc2cc0b 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IEventListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IEventListenerConfiguration.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public interface IEventListenerConfiguration { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs similarity index 86% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs index 30401bb072..1fbfefa420 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationListenerConfiguration.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public interface IIntegrationListenerConfiguration : IEventListenerConfiguration { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationMessage.cs similarity index 77% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationMessage.cs index 5b6bfe2e53..2d333dfee4 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IIntegrationMessage.cs @@ -1,6 +1,6 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public interface IIntegrationMessage { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFailureCategory.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFailureCategory.cs similarity index 93% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFailureCategory.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFailureCategory.cs index 544e671d51..f9d8f2ab68 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFailureCategory.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFailureCategory.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; /// /// Categories of event integration failures used for classification and retry logic. diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterGroup.cs similarity index 76% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterGroup.cs index 276ca3a14b..0c129883cf 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterGroup.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class IntegrationFilterGroup { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterOperation.cs similarity index 61% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterOperation.cs index fddf630e26..d98ab1e13e 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterOperation.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public enum IntegrationFilterOperation { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterRule.cs similarity index 76% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterRule.cs index b5f90f5e63..9ac3ef753e 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationFilterRule.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class IntegrationFilterRule { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResult.cs similarity index 97% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResult.cs index 375f2489cb..bbdce50ec0 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResult.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; /// /// Represents the result of an integration handler operation, including success status, diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationMessage.cs similarity index 93% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationMessage.cs index b0fc2161ba..edf31a2a1f 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationMessage.cs @@ -1,7 +1,7 @@ using System.Text.Json; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class IntegrationMessage : IIntegrationMessage { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthState.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationOAuthState.cs similarity index 95% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthState.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationOAuthState.cs index 3b29bbebb4..d75780d6c6 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthState.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationOAuthState.cs @@ -1,8 +1,8 @@ using System.Security.Cryptography; using System.Text; -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class IntegrationOAuthState { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContext.cs similarity index 97% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContext.cs index c44e550d15..3b527469fa 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContext.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class IntegrationTemplateContext(EventMessage eventMessage) { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/ListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/ListenerConfiguration.cs similarity index 94% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/ListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/ListenerConfiguration.cs index 40eb2b3e77..2a970ce670 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/ListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/ListenerConfiguration.cs @@ -1,6 +1,6 @@ using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public abstract class ListenerConfiguration { diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs b/src/Core/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetails.cs similarity index 95% rename from src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetails.cs index 5fdc760c90..6517ceccf0 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetails.cs @@ -1,9 +1,8 @@ using System.Text.Json.Nodes; +using Bit.Core.Dirt.Enums; using Bit.Core.Enums; -#nullable enable - -namespace Bit.Core.Models.Data.Organizations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class OrganizationIntegrationConfigurationDetails { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs similarity index 87% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs index 118b3a17fe..20299dd651 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/RepositoryListenerConfiguration.cs @@ -1,6 +1,6 @@ using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class RepositoryListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IEventListenerConfiguration diff --git a/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegration.cs new file mode 100644 index 0000000000..fcfd07f574 --- /dev/null +++ b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegration.cs @@ -0,0 +1,3 @@ +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; + +public record SlackIntegration(string Token); diff --git a/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs new file mode 100644 index 0000000000..164a132e8c --- /dev/null +++ b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs @@ -0,0 +1,3 @@ +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; + +public record SlackIntegrationConfiguration(string ChannelId); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs similarity index 56% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs index d22f43bb92..b81617118d 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record SlackIntegrationConfigurationDetails(string ChannelId, string Token); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/SlackListenerConfiguration.cs similarity index 91% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/SlackListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/SlackListenerConfiguration.cs index 7dd834f51e..ef2cf83837 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/SlackListenerConfiguration.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class SlackListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegration.cs similarity index 71% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegration.cs index 8390022839..fcb42a5261 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegration.cs @@ -1,6 +1,6 @@ -using Bit.Core.Models.Teams; +using Bit.Core.Dirt.Models.Data.Teams; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record TeamsIntegration( string TenantId, diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs similarity index 56% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs index 66fe558dff..a890f553f5 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsIntegrationConfigurationDetails.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record TeamsIntegrationConfigurationDetails(string ChannelId, Uri ServiceUrl); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs similarity index 91% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs index 24cf674648..4111c96601 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/TeamsListenerConfiguration.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class TeamsListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegration.cs similarity index 57% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegration.cs index dcda4caa92..d12ea16ee1 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegration.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record WebhookIntegration(Uri Uri, string? Scheme = null, string? Token = null); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs similarity index 60% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs index 851bd3f411..8d7bf90e2c 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record WebhookIntegrationConfiguration(Uri Uri, string? Scheme = null, string? Token = null); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs similarity index 62% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs index dba9b1714d..49508f8454 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs @@ -1,3 +1,3 @@ -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public record WebhookIntegrationConfigurationDetails(Uri Uri, string? Scheme = null, string? Token = null); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs similarity index 91% rename from src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs rename to src/Core/Dirt/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs index 9d5bf811c7..9afc26168c 100644 --- a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs +++ b/src/Core/Dirt/Models/Data/EventIntegrations/WebhookListenerConfiguration.cs @@ -1,7 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Bit.Core.Settings; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; public class WebhookListenerConfiguration(GlobalSettings globalSettings) : ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration diff --git a/src/Core/AdminConsole/Models/Slack/SlackApiResponse.cs b/src/Core/Dirt/Models/Data/Slack/SlackApiResponse.cs similarity index 97% rename from src/Core/AdminConsole/Models/Slack/SlackApiResponse.cs rename to src/Core/Dirt/Models/Data/Slack/SlackApiResponse.cs index 3c811e2b28..a70e623ae3 100644 --- a/src/Core/AdminConsole/Models/Slack/SlackApiResponse.cs +++ b/src/Core/Dirt/Models/Data/Slack/SlackApiResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Bit.Core.Models.Slack; +namespace Bit.Core.Dirt.Models.Data.Slack; public abstract class SlackApiResponse { diff --git a/src/Core/AdminConsole/Models/Teams/TeamsApiResponse.cs b/src/Core/Dirt/Models/Data/Teams/TeamsApiResponse.cs similarity index 97% rename from src/Core/AdminConsole/Models/Teams/TeamsApiResponse.cs rename to src/Core/Dirt/Models/Data/Teams/TeamsApiResponse.cs index 131e45264f..b4b6a2542d 100644 --- a/src/Core/AdminConsole/Models/Teams/TeamsApiResponse.cs +++ b/src/Core/Dirt/Models/Data/Teams/TeamsApiResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Bit.Core.Models.Teams; +namespace Bit.Core.Dirt.Models.Data.Teams; /// Represents the response returned by the Microsoft OAuth 2.0 token endpoint. /// See Microsoft identity platform and OAuth 2.0 diff --git a/src/Core/AdminConsole/Models/Teams/TeamsBotCredentialProvider.cs b/src/Core/Dirt/Models/Data/Teams/TeamsBotCredentialProvider.cs similarity index 94% rename from src/Core/AdminConsole/Models/Teams/TeamsBotCredentialProvider.cs rename to src/Core/Dirt/Models/Data/Teams/TeamsBotCredentialProvider.cs index eeb17131a3..d8740f9e90 100644 --- a/src/Core/AdminConsole/Models/Teams/TeamsBotCredentialProvider.cs +++ b/src/Core/Dirt/Models/Data/Teams/TeamsBotCredentialProvider.cs @@ -1,6 +1,6 @@ using Microsoft.Bot.Connector.Authentication; -namespace Bit.Core.AdminConsole.Models.Teams; +namespace Bit.Core.Dirt.Models.Data.Teams; public class TeamsBotCredentialProvider(string clientId, string clientSecret) : ICredentialProvider { diff --git a/src/Core/AdminConsole/Repositories/IOrganizationIntegrationConfigurationRepository.cs b/src/Core/Dirt/Repositories/IOrganizationIntegrationConfigurationRepository.cs similarity index 88% rename from src/Core/AdminConsole/Repositories/IOrganizationIntegrationConfigurationRepository.cs rename to src/Core/Dirt/Repositories/IOrganizationIntegrationConfigurationRepository.cs index fb42ffa000..f6f90c7c9f 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationIntegrationConfigurationRepository.cs +++ b/src/Core/Dirt/Repositories/IOrganizationIntegrationConfigurationRepository.cs @@ -1,8 +1,10 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; +using Bit.Core.Repositories; -namespace Bit.Core.Repositories; +namespace Bit.Core.Dirt.Repositories; public interface IOrganizationIntegrationConfigurationRepository : IRepository { diff --git a/src/Core/AdminConsole/Repositories/IOrganizationIntegrationRepository.cs b/src/Core/Dirt/Repositories/IOrganizationIntegrationRepository.cs similarity index 74% rename from src/Core/AdminConsole/Repositories/IOrganizationIntegrationRepository.cs rename to src/Core/Dirt/Repositories/IOrganizationIntegrationRepository.cs index 1d8b8be0ec..03775e8d20 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationIntegrationRepository.cs +++ b/src/Core/Dirt/Repositories/IOrganizationIntegrationRepository.cs @@ -1,6 +1,7 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; +using Bit.Core.Repositories; -namespace Bit.Core.Repositories; +namespace Bit.Core.Dirt.Repositories; public interface IOrganizationIntegrationRepository : IRepository { diff --git a/src/Core/AdminConsole/Services/IAzureServiceBusService.cs b/src/Core/Dirt/Services/IAzureServiceBusService.cs similarity index 77% rename from src/Core/AdminConsole/Services/IAzureServiceBusService.cs rename to src/Core/Dirt/Services/IAzureServiceBusService.cs index 75864255c2..6b425511ab 100644 --- a/src/Core/AdminConsole/Services/IAzureServiceBusService.cs +++ b/src/Core/Dirt/Services/IAzureServiceBusService.cs @@ -1,7 +1,7 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IAzureServiceBusService : IEventIntegrationPublisher, IAsyncDisposable { diff --git a/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs b/src/Core/Dirt/Services/IEventIntegrationPublisher.cs similarity index 67% rename from src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs rename to src/Core/Dirt/Services/IEventIntegrationPublisher.cs index 4d95707e90..583c2448fe 100644 --- a/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs +++ b/src/Core/Dirt/Services/IEventIntegrationPublisher.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IEventIntegrationPublisher : IAsyncDisposable { diff --git a/src/Core/AdminConsole/Services/IEventMessageHandler.cs b/src/Core/Dirt/Services/IEventMessageHandler.cs similarity index 85% rename from src/Core/AdminConsole/Services/IEventMessageHandler.cs rename to src/Core/Dirt/Services/IEventMessageHandler.cs index 83c5e33ecb..9b1385129b 100644 --- a/src/Core/AdminConsole/Services/IEventMessageHandler.cs +++ b/src/Core/Dirt/Services/IEventMessageHandler.cs @@ -1,6 +1,6 @@ using Bit.Core.Models.Data; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IEventMessageHandler { diff --git a/src/Core/AdminConsole/Services/IIntegrationFilterService.cs b/src/Core/Dirt/Services/IIntegrationFilterService.cs similarity index 67% rename from src/Core/AdminConsole/Services/IIntegrationFilterService.cs rename to src/Core/Dirt/Services/IIntegrationFilterService.cs index 5bc035d468..f46ab83f54 100644 --- a/src/Core/AdminConsole/Services/IIntegrationFilterService.cs +++ b/src/Core/Dirt/Services/IIntegrationFilterService.cs @@ -1,9 +1,9 @@ #nullable enable -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Models.Data; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IIntegrationFilterService { diff --git a/src/Core/AdminConsole/Services/IIntegrationHandler.cs b/src/Core/Dirt/Services/IIntegrationHandler.cs similarity index 98% rename from src/Core/AdminConsole/Services/IIntegrationHandler.cs rename to src/Core/Dirt/Services/IIntegrationHandler.cs index c36081cb52..81103b453d 100644 --- a/src/Core/AdminConsole/Services/IIntegrationHandler.cs +++ b/src/Core/Dirt/Services/IIntegrationHandler.cs @@ -1,8 +1,8 @@ using System.Globalization; using System.Net; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IIntegrationHandler { diff --git a/src/Core/AdminConsole/Services/IOrganizationIntegrationConfigurationValidator.cs b/src/Core/Dirt/Services/IOrganizationIntegrationConfigurationValidator.cs similarity index 86% rename from src/Core/AdminConsole/Services/IOrganizationIntegrationConfigurationValidator.cs rename to src/Core/Dirt/Services/IOrganizationIntegrationConfigurationValidator.cs index 48346cbae7..4a3a089f26 100644 --- a/src/Core/AdminConsole/Services/IOrganizationIntegrationConfigurationValidator.cs +++ b/src/Core/Dirt/Services/IOrganizationIntegrationConfigurationValidator.cs @@ -1,7 +1,7 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; -namespace Bit.Core.AdminConsole.Services; +namespace Bit.Core.Dirt.Services; public interface IOrganizationIntegrationConfigurationValidator { diff --git a/src/Core/AdminConsole/Services/IRabbitMqService.cs b/src/Core/Dirt/Services/IRabbitMqService.cs similarity index 89% rename from src/Core/AdminConsole/Services/IRabbitMqService.cs rename to src/Core/Dirt/Services/IRabbitMqService.cs index 12c40c3b98..b9f824506f 100644 --- a/src/Core/AdminConsole/Services/IRabbitMqService.cs +++ b/src/Core/Dirt/Services/IRabbitMqService.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using RabbitMQ.Client; using RabbitMQ.Client.Events; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; public interface IRabbitMqService : IEventIntegrationPublisher { diff --git a/src/Core/AdminConsole/Services/ISlackService.cs b/src/Core/Dirt/Services/ISlackService.cs similarity index 97% rename from src/Core/AdminConsole/Services/ISlackService.cs rename to src/Core/Dirt/Services/ISlackService.cs index 60d3da8af4..111fcb5440 100644 --- a/src/Core/AdminConsole/Services/ISlackService.cs +++ b/src/Core/Dirt/Services/ISlackService.cs @@ -1,6 +1,7 @@ -using Bit.Core.Models.Slack; +using Bit.Core.Dirt.Models.Data.Slack; +using Bit.Core.Dirt.Services.Implementations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; /// Defines operations for interacting with Slack, including OAuth authentication, channel discovery, /// and sending messages. diff --git a/src/Core/AdminConsole/Services/ITeamsService.cs b/src/Core/Dirt/Services/ITeamsService.cs similarity index 95% rename from src/Core/AdminConsole/Services/ITeamsService.cs rename to src/Core/Dirt/Services/ITeamsService.cs index e3757987c3..30a324f9a4 100644 --- a/src/Core/AdminConsole/Services/ITeamsService.cs +++ b/src/Core/Dirt/Services/ITeamsService.cs @@ -1,6 +1,7 @@ -using Bit.Core.Models.Teams; +using Bit.Core.Dirt.Models.Data.Teams; +using Bit.Core.Dirt.Services.Implementations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services; /// /// Service that provides functionality relating to the Microsoft Teams integration including OAuth, diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs b/src/Core/Dirt/Services/Implementations/AzureServiceBusEventListenerService.cs similarity index 89% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs rename to src/Core/Dirt/Services/Implementations/AzureServiceBusEventListenerService.cs index a589211687..6175374e2f 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs +++ b/src/Core/Dirt/Services/Implementations/AzureServiceBusEventListenerService.cs @@ -1,9 +1,9 @@ using System.Text; using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class AzureServiceBusEventListenerService : EventLoggingListenerService where TConfiguration : IEventListenerConfiguration @@ -42,7 +42,7 @@ public class AzureServiceBusEventListenerService : EventLoggingL private static ILogger CreateLogger(ILoggerFactory loggerFactory, TConfiguration configuration) { return loggerFactory.CreateLogger( - categoryName: $"Bit.Core.Services.AzureServiceBusEventListenerService.{configuration.EventSubscriptionName}"); + categoryName: $"Bit.Core.Dirt.Services.Implementations.AzureServiceBusEventListenerService.{configuration.EventSubscriptionName}"); } internal Task ProcessErrorAsync(ProcessErrorEventArgs args) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusIntegrationListenerService.cs b/src/Core/Dirt/Services/Implementations/AzureServiceBusIntegrationListenerService.cs similarity index 94% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusIntegrationListenerService.cs rename to src/Core/Dirt/Services/Implementations/AzureServiceBusIntegrationListenerService.cs index c97c5f7efe..32132ddb37 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusIntegrationListenerService.cs +++ b/src/Core/Dirt/Services/Implementations/AzureServiceBusIntegrationListenerService.cs @@ -1,9 +1,9 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class AzureServiceBusIntegrationListenerService : BackgroundService where TConfiguration : IIntegrationListenerConfiguration @@ -23,7 +23,7 @@ public class AzureServiceBusIntegrationListenerService : Backgro { _handler = handler; _logger = loggerFactory.CreateLogger( - categoryName: $"Bit.Core.Services.AzureServiceBusIntegrationListenerService.{configuration.IntegrationSubscriptionName}"); + categoryName: $"Bit.Core.Dirt.Services.Implementations.AzureServiceBusIntegrationListenerService.{configuration.IntegrationSubscriptionName}"); _maxRetries = configuration.MaxRetries; _serviceBusService = serviceBusService; diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs b/src/Core/Dirt/Services/Implementations/AzureServiceBusService.cs similarity index 94% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs rename to src/Core/Dirt/Services/Implementations/AzureServiceBusService.cs index 953a9bb56e..7b87850fe3 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs +++ b/src/Core/Dirt/Services/Implementations/AzureServiceBusService.cs @@ -1,9 +1,9 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Settings; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class AzureServiceBusService : IAzureServiceBusService { diff --git a/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs b/src/Core/Dirt/Services/Implementations/AzureTableStorageEventHandler.cs similarity index 84% rename from src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs rename to src/Core/Dirt/Services/Implementations/AzureTableStorageEventHandler.cs index 578dde9485..73d22b21a7 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs +++ b/src/Core/Dirt/Services/Implementations/AzureTableStorageEventHandler.cs @@ -1,9 +1,8 @@ -#nullable enable - -using Bit.Core.Models.Data; +using Bit.Core.Models.Data; +using Bit.Core.Services; using Microsoft.Extensions.DependencyInjection; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class AzureTableStorageEventHandler( [FromKeyedServices("persistent")] IEventWriteService eventWriteService) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/DatadogIntegrationHandler.cs b/src/Core/Dirt/Services/Implementations/DatadogIntegrationHandler.cs similarity index 90% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/DatadogIntegrationHandler.cs rename to src/Core/Dirt/Services/Implementations/DatadogIntegrationHandler.cs index 45bb5b6d7d..e5c684ceec 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/DatadogIntegrationHandler.cs +++ b/src/Core/Dirt/Services/Implementations/DatadogIntegrationHandler.cs @@ -1,7 +1,7 @@ using System.Text; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class DatadogIntegrationHandler( IHttpClientFactory httpClientFactory, diff --git a/src/Core/Dirt/Services/Implementations/EventIntegrationEventWriteService.cs b/src/Core/Dirt/Services/Implementations/EventIntegrationEventWriteService.cs index 4ac97df763..44e0513ee0 100644 --- a/src/Core/Dirt/Services/Implementations/EventIntegrationEventWriteService.cs +++ b/src/Core/Dirt/Services/Implementations/EventIntegrationEventWriteService.cs @@ -1,7 +1,8 @@ using System.Text.Json; using Bit.Core.Models.Data; +using Bit.Core.Services; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class EventIntegrationEventWriteService : IEventWriteService, IAsyncDisposable { private readonly IEventIntegrationPublisher _eventIntegrationPublisher; diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs b/src/Core/Dirt/Services/Implementations/EventIntegrationHandler.cs similarity index 97% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs rename to src/Core/Dirt/Services/Implementations/EventIntegrationHandler.cs index b4246884f7..bcd1f1dd8c 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs +++ b/src/Core/Dirt/Services/Implementations/EventIntegrationHandler.cs @@ -1,18 +1,18 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Utilities; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Models.Data; -using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class EventIntegrationHandler( IntegrationType integrationType, diff --git a/src/Core/AdminConsole/Services/EventLoggingListenerService.cs b/src/Core/Dirt/Services/Implementations/EventLoggingListenerService.cs similarity index 97% rename from src/Core/AdminConsole/Services/EventLoggingListenerService.cs rename to src/Core/Dirt/Services/Implementations/EventLoggingListenerService.cs index 84a862ce94..29e3f8dec3 100644 --- a/src/Core/AdminConsole/Services/EventLoggingListenerService.cs +++ b/src/Core/Dirt/Services/Implementations/EventLoggingListenerService.cs @@ -1,11 +1,9 @@ -#nullable enable - -using System.Text.Json; +using System.Text.Json; using Bit.Core.Models.Data; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public abstract class EventLoggingListenerService : BackgroundService { diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRepositoryHandler.cs b/src/Core/Dirt/Services/Implementations/EventRepositoryHandler.cs similarity index 87% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRepositoryHandler.cs rename to src/Core/Dirt/Services/Implementations/EventRepositoryHandler.cs index ee3a2d5db2..32173b8da0 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRepositoryHandler.cs +++ b/src/Core/Dirt/Services/Implementations/EventRepositoryHandler.cs @@ -1,7 +1,8 @@ using Bit.Core.Models.Data; +using Bit.Core.Services; using Microsoft.Extensions.DependencyInjection; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class EventRepositoryHandler( [FromKeyedServices("persistent")] IEventWriteService eventWriteService) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs b/src/Core/Dirt/Services/Implementations/IntegrationFilterFactory.cs similarity index 97% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs rename to src/Core/Dirt/Services/Implementations/IntegrationFilterFactory.cs index d28ac910b7..8c25c80208 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs +++ b/src/Core/Dirt/Services/Implementations/IntegrationFilterFactory.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using Bit.Core.Models.Data; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public delegate bool IntegrationFilter(EventMessage message, object? value); diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs b/src/Core/Dirt/Services/Implementations/IntegrationFilterService.cs similarity index 97% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs rename to src/Core/Dirt/Services/Implementations/IntegrationFilterService.cs index 1c8fae4000..7d56b7c7ce 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs +++ b/src/Core/Dirt/Services/Implementations/IntegrationFilterService.cs @@ -1,8 +1,8 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Models.Data; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class IntegrationFilterService : IIntegrationFilterService { diff --git a/src/Core/AdminConsole/Services/OrganizationIntegrationConfigurationValidator.cs b/src/Core/Dirt/Services/Implementations/OrganizationIntegrationConfigurationValidator.cs similarity index 92% rename from src/Core/AdminConsole/Services/OrganizationIntegrationConfigurationValidator.cs rename to src/Core/Dirt/Services/Implementations/OrganizationIntegrationConfigurationValidator.cs index 2769565675..7b6ab320b8 100644 --- a/src/Core/AdminConsole/Services/OrganizationIntegrationConfigurationValidator.cs +++ b/src/Core/Dirt/Services/Implementations/OrganizationIntegrationConfigurationValidator.cs @@ -1,9 +1,9 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.AdminConsole.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class OrganizationIntegrationConfigurationValidator : IOrganizationIntegrationConfigurationValidator { diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqEventListenerService.cs b/src/Core/Dirt/Services/Implementations/RabbitMqEventListenerService.cs similarity index 91% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqEventListenerService.cs rename to src/Core/Dirt/Services/Implementations/RabbitMqEventListenerService.cs index 430540a2f7..ca7cd5ef16 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqEventListenerService.cs +++ b/src/Core/Dirt/Services/Implementations/RabbitMqEventListenerService.cs @@ -1,10 +1,10 @@ using System.Text; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Microsoft.Extensions.Logging; using RabbitMQ.Client; using RabbitMQ.Client.Events; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class RabbitMqEventListenerService : EventLoggingListenerService where TConfiguration : IEventListenerConfiguration @@ -69,6 +69,6 @@ public class RabbitMqEventListenerService : EventLoggingListener private static ILogger CreateLogger(ILoggerFactory loggerFactory, TConfiguration configuration) { return loggerFactory.CreateLogger( - categoryName: $"Bit.Core.Services.RabbitMqEventListenerService.{configuration.EventQueueName}"); + categoryName: $"Bit.Core.Dirt.Services.Implementations.RabbitMqEventListenerService.{configuration.EventQueueName}"); } } diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs b/src/Core/Dirt/Services/Implementations/RabbitMqIntegrationListenerService.cs similarity index 96% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs rename to src/Core/Dirt/Services/Implementations/RabbitMqIntegrationListenerService.cs index 0762edc040..eced9131bb 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs +++ b/src/Core/Dirt/Services/Implementations/RabbitMqIntegrationListenerService.cs @@ -1,12 +1,12 @@ using System.Text; using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using RabbitMQ.Client; using RabbitMQ.Client.Events; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class RabbitMqIntegrationListenerService : BackgroundService where TConfiguration : IIntegrationListenerConfiguration @@ -37,7 +37,7 @@ public class RabbitMqIntegrationListenerService : BackgroundServ _timeProvider = timeProvider; _lazyChannel = new Lazy>(() => _rabbitMqService.CreateChannelAsync()); _logger = loggerFactory.CreateLogger( - categoryName: $"Bit.Core.Services.RabbitMqIntegrationListenerService.{configuration.IntegrationQueueName}"); ; + categoryName: $"Bit.Core.Dirt.Services.Implementations.RabbitMqIntegrationListenerService.{configuration.IntegrationQueueName}"); ; } public override async Task StartAsync(CancellationToken cancellationToken) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs b/src/Core/Dirt/Services/Implementations/RabbitMqService.cs similarity index 98% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs rename to src/Core/Dirt/Services/Implementations/RabbitMqService.cs index 8976530cf4..c27fb37d08 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs +++ b/src/Core/Dirt/Services/Implementations/RabbitMqService.cs @@ -1,11 +1,11 @@ using System.Text; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Settings; using RabbitMQ.Client; using RabbitMQ.Client.Events; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class RabbitMqService : IRabbitMqService { diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs b/src/Core/Dirt/Services/Implementations/SlackIntegrationHandler.cs similarity index 96% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs rename to src/Core/Dirt/Services/Implementations/SlackIntegrationHandler.cs index e681140afe..6c6a4dd356 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs +++ b/src/Core/Dirt/Services/Implementations/SlackIntegrationHandler.cs @@ -1,6 +1,6 @@ -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class SlackIntegrationHandler( ISlackService slackService) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackService.cs b/src/Core/Dirt/Services/Implementations/SlackService.cs similarity index 98% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackService.cs rename to src/Core/Dirt/Services/Implementations/SlackService.cs index 7eec2ec374..7683f718b5 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackService.cs +++ b/src/Core/Dirt/Services/Implementations/SlackService.cs @@ -2,11 +2,11 @@ using System.Net.Http.Json; using System.Text.Json; using System.Web; -using Bit.Core.Models.Slack; +using Bit.Core.Dirt.Models.Data.Slack; using Bit.Core.Settings; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class SlackService( IHttpClientFactory httpClientFactory, diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsIntegrationHandler.cs b/src/Core/Dirt/Services/Implementations/TeamsIntegrationHandler.cs similarity index 94% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsIntegrationHandler.cs rename to src/Core/Dirt/Services/Implementations/TeamsIntegrationHandler.cs index 9e3645a99f..7aaed6c647 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsIntegrationHandler.cs +++ b/src/Core/Dirt/Services/Implementations/TeamsIntegrationHandler.cs @@ -1,8 +1,8 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Microsoft.Rest; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class TeamsIntegrationHandler( ITeamsService teamsService) diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsService.cs b/src/Core/Dirt/Services/Implementations/TeamsService.cs similarity index 96% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsService.cs rename to src/Core/Dirt/Services/Implementations/TeamsService.cs index f9911760bb..edb43bf85e 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/TeamsService.cs +++ b/src/Core/Dirt/Services/Implementations/TeamsService.cs @@ -2,9 +2,9 @@ using System.Net.Http.Json; using System.Text.Json; using System.Web; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Models.Teams; -using Bit.Core.Repositories; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Teams; +using Bit.Core.Dirt.Repositories; using Bit.Core.Settings; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Teams; @@ -12,9 +12,9 @@ using Microsoft.Bot.Connector; using Microsoft.Bot.Connector.Authentication; using Microsoft.Bot.Schema; using Microsoft.Extensions.Logging; -using TeamInfo = Bit.Core.Models.Teams.TeamInfo; +using TeamInfo = Bit.Core.Dirt.Models.Data.Teams.TeamInfo; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class TeamsService( IHttpClientFactory httpClientFactory, diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs b/src/Core/Dirt/Services/Implementations/WebhookIntegrationHandler.cs similarity index 92% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs rename to src/Core/Dirt/Services/Implementations/WebhookIntegrationHandler.cs index 0599f6e9d4..6caa1b9a6e 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs +++ b/src/Core/Dirt/Services/Implementations/WebhookIntegrationHandler.cs @@ -1,8 +1,8 @@ using System.Net.Http.Headers; using System.Text; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.Services; +namespace Bit.Core.Dirt.Services.Implementations; public class WebhookIntegrationHandler( IHttpClientFactory httpClientFactory, diff --git a/src/Core/AdminConsole/Services/NoopImplementations/NoopSlackService.cs b/src/Core/Dirt/Services/NoopImplementations/NoopSlackService.cs similarity index 88% rename from src/Core/AdminConsole/Services/NoopImplementations/NoopSlackService.cs rename to src/Core/Dirt/Services/NoopImplementations/NoopSlackService.cs index a54df94814..30b68186bc 100644 --- a/src/Core/AdminConsole/Services/NoopImplementations/NoopSlackService.cs +++ b/src/Core/Dirt/Services/NoopImplementations/NoopSlackService.cs @@ -1,7 +1,6 @@ -using Bit.Core.Models.Slack; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.Slack; -namespace Bit.Core.AdminConsole.Services.NoopImplementations; +namespace Bit.Core.Dirt.Services.NoopImplementations; public class NoopSlackService : ISlackService { diff --git a/src/Core/AdminConsole/Services/NoopImplementations/NoopTeamsService.cs b/src/Core/Dirt/Services/NoopImplementations/NoopTeamsService.cs similarity index 83% rename from src/Core/AdminConsole/Services/NoopImplementations/NoopTeamsService.cs rename to src/Core/Dirt/Services/NoopImplementations/NoopTeamsService.cs index fafb23f570..3ebd58d996 100644 --- a/src/Core/AdminConsole/Services/NoopImplementations/NoopTeamsService.cs +++ b/src/Core/Dirt/Services/NoopImplementations/NoopTeamsService.cs @@ -1,7 +1,6 @@ -using Bit.Core.Models.Teams; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.Teams; -namespace Bit.Core.AdminConsole.Services.NoopImplementations; +namespace Bit.Core.Dirt.Services.NoopImplementations; public class NoopTeamsService : ITeamsService { diff --git a/src/Core/Utilities/EventIntegrationsCacheConstants.cs b/src/Core/Utilities/EventIntegrationsCacheConstants.cs index 19cc3f949c..000a9c230e 100644 --- a/src/Core/Utilities/EventIntegrationsCacheConstants.cs +++ b/src/Core/Utilities/EventIntegrationsCacheConstants.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; namespace Bit.Core.Utilities; diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 445ff77109..e3ee82270f 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ using Bit.Infrastructure.Dapper.AdminConsole.Repositories; using Bit.Infrastructure.Dapper.Auth.Repositories; using Bit.Infrastructure.Dapper.Billing.Repositories; using Bit.Infrastructure.Dapper.Dirt; +using Bit.Infrastructure.Dapper.Dirt.Repositories; using Bit.Infrastructure.Dapper.KeyManagement.Repositories; using Bit.Infrastructure.Dapper.NotificationCenter.Repositories; using Bit.Infrastructure.Dapper.Platform; diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs b/src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs similarity index 93% rename from src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs rename to src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs index af24e11a0e..2b6b45f3c8 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs @@ -1,14 +1,15 @@ using System.Data; -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; -using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; -namespace Bit.Infrastructure.Dapper.AdminConsole.Repositories; +namespace Bit.Infrastructure.Dapper.Dirt.Repositories; public class OrganizationIntegrationConfigurationRepository : Repository, IOrganizationIntegrationConfigurationRepository { diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationRepository.cs b/src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationRepository.cs similarity index 90% rename from src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationRepository.cs rename to src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationRepository.cs index 4f8fb979d3..a094bbc669 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationIntegrationRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/Repositories/OrganizationIntegrationRepository.cs @@ -1,11 +1,12 @@ using System.Data; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Repositories; +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.Repositories; +namespace Bit.Infrastructure.Dapper.Dirt.Repositories; public class OrganizationIntegrationRepository : Repository, IOrganizationIntegrationRepository { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationConfigurationEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationConfigurationEntityTypeConfiguration.cs index 935473deaa..bc57c8ed15 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationConfigurationEntityTypeConfiguration.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationConfigurationEntityTypeConfiguration.cs @@ -1,4 +1,4 @@ -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; +using Bit.Infrastructure.EntityFramework.Dirt.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationEntityTypeConfiguration.cs index 3434d735d0..b14c156832 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationEntityTypeConfiguration.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Configurations/OrganizationIntegrationEntityTypeConfiguration.cs @@ -1,4 +1,4 @@ -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; +using Bit.Infrastructure.EntityFramework.Dirt.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegration.cs deleted file mode 100644 index 0f47d5947b..0000000000 --- a/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AutoMapper; - -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; - -public class OrganizationIntegration : Core.AdminConsole.Entities.OrganizationIntegration -{ - public virtual required Organization Organization { get; set; } -} - -public class OrganizationIntegrationMapperProfile : Profile -{ - public OrganizationIntegrationMapperProfile() - { - CreateMap().ReverseMap(); - } -} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegrationConfiguration.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegrationConfiguration.cs deleted file mode 100644 index 21b282f767..0000000000 --- a/src/Infrastructure.EntityFramework/AdminConsole/Models/OrganizationIntegrationConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AutoMapper; - -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; - -public class OrganizationIntegrationConfiguration : Core.AdminConsole.Entities.OrganizationIntegrationConfiguration -{ - public virtual required OrganizationIntegration OrganizationIntegration { get; set; } -} - -public class OrganizationIntegrationConfigurationMapperProfile : Profile -{ - public OrganizationIntegrationConfigurationMapperProfile() - { - CreateMap().ReverseMap(); - } -} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegration.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegration.cs new file mode 100644 index 0000000000..f3472915a9 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegration.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; + +public class OrganizationIntegration : Core.Dirt.Entities.OrganizationIntegration +{ + public virtual required Organization Organization { get; set; } +} + +public class OrganizationIntegrationMapperProfile : Profile +{ + public OrganizationIntegrationMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegrationConfiguration.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegrationConfiguration.cs new file mode 100644 index 0000000000..11632d6530 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationIntegrationConfiguration.cs @@ -0,0 +1,16 @@ +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; + +public class OrganizationIntegrationConfiguration : Core.Dirt.Entities.OrganizationIntegrationConfiguration +{ + public virtual required OrganizationIntegration OrganizationIntegration { get; set; } +} + +public class OrganizationIntegrationConfigurationMapperProfile : Profile +{ + public OrganizationIntegrationConfigurationMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs similarity index 75% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs index ff8f92fd91..b0d545d3c3 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationConfigurationRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationConfigurationRepository.cs @@ -1,17 +1,17 @@ using AutoMapper; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; -using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; +using Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Repositories; -using Bit.Infrastructure.EntityFramework.Repositories.Queries; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using OrganizationIntegrationConfiguration = Bit.Core.Dirt.Entities.OrganizationIntegrationConfiguration; -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; -public class OrganizationIntegrationConfigurationRepository : Repository, IOrganizationIntegrationConfigurationRepository +public class OrganizationIntegrationConfigurationRepository : Repository, IOrganizationIntegrationConfigurationRepository { public OrganizationIntegrationConfigurationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, context => context.OrganizationIntegrationConfigurations) @@ -43,7 +43,7 @@ public class OrganizationIntegrationConfigurationRepository : Repository> GetManyByIntegrationAsync( + public async Task> GetManyByIntegrationAsync( Guid organizationIntegrationId) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationRepository.cs similarity index 67% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationRepository.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationRepository.cs index c11591efcd..cbcd574854 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationIntegrationRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationIntegrationRepository.cs @@ -1,15 +1,15 @@ using AutoMapper; -using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; +using Bit.Core.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using OrganizationIntegration = Bit.Core.Dirt.Entities.OrganizationIntegration; -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; public class OrganizationIntegrationRepository : - Repository, + Repository, IOrganizationIntegrationRepository { public OrganizationIntegrationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) @@ -17,7 +17,7 @@ public class OrganizationIntegrationRepository : { } - public async Task> GetManyByOrganizationAsync(Guid organizationId) + public async Task> GetManyByOrganizationAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -27,7 +27,7 @@ public class OrganizationIntegrationRepository : } } - public async Task GetByTeamsConfigurationTenantIdTeamId( + public async Task GetByTeamsConfigurationTenantIdTeamId( string tenantId, string teamId) { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs similarity index 82% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs index 421bb9407a..25fd06c04d 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs @@ -1,7 +1,10 @@ -using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Enums; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories.Queries; -namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; public class OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery( Guid organizationId, diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs similarity index 82% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs index 8141292c81..4d5be520d2 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyQuery.cs @@ -1,8 +1,8 @@ -#nullable enable +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories.Queries; -using Bit.Core.Models.Data.Organizations; - -namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; public class OrganizationIntegrationConfigurationDetailsReadManyQuery : IQuery { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs similarity index 91% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs index 3ed3a48723..3ae2f5f66d 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; public class OrganizationIntegrationConfigurationReadManyByOrganizationIntegrationIdQuery : IQuery { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs similarity index 89% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs index a1e86d9add..fd06c6d296 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery.cs @@ -1,9 +1,9 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; public class OrganizationIntegrationReadByTeamsConfigurationTenantIdTeamIdQuery : IQuery { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs similarity index 88% rename from src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs rename to src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs index df87ad0bc1..477983ebab 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/Queries/OrganizationIntegrationReadManyByOrganizationIdQuery.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; -namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories.Queries; public class OrganizationIntegrationReadManyByOrganizationIdQuery : IQuery { diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationControllerTests.cs b/test/Api.Test/Dirt/Controllers/OrganizationIntegrationControllerTests.cs similarity index 95% rename from test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationControllerTests.cs rename to test/Api.Test/Dirt/Controllers/OrganizationIntegrationControllerTests.cs index c9131f3505..85f4e7ca7f 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationControllerTests.cs +++ b/test/Api.Test/Dirt/Controllers/OrganizationIntegrationControllerTests.cs @@ -1,10 +1,10 @@ -using Bit.Api.AdminConsole.Controllers; -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Api.Dirt.Controllers; +using Bit.Api.Dirt.Models.Request; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc; using NSubstitute; using Xunit; -namespace Bit.Api.Test.AdminConsole.Controllers; +namespace Bit.Api.Test.Dirt.Controllers; [ControllerCustomize(typeof(OrganizationIntegrationController))] [SutProviderCustomize] diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs b/test/Api.Test/Dirt/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs similarity index 96% rename from test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs rename to test/Api.Test/Dirt/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs index 6e1dadb92f..ec8e5c3e36 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs +++ b/test/Api.Test/Dirt/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs @@ -1,9 +1,9 @@ -using Bit.Api.AdminConsole.Controllers; -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Api.Dirt.Controllers; +using Bit.Api.Dirt.Models.Request; +using Bit.Api.Dirt.Models.Response; using Bit.Core.Context; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc; using NSubstitute; using Xunit; -namespace Bit.Api.Test.AdminConsole.Controllers; +namespace Bit.Api.Test.Dirt.Controllers; [ControllerCustomize(typeof(OrganizationIntegrationConfigurationController))] [SutProviderCustomize] diff --git a/test/Api.Test/AdminConsole/Controllers/SlackIntegrationControllerTests.cs b/test/Api.Test/Dirt/Controllers/SlackIntegrationControllerTests.cs similarity index 98% rename from test/Api.Test/AdminConsole/Controllers/SlackIntegrationControllerTests.cs rename to test/Api.Test/Dirt/Controllers/SlackIntegrationControllerTests.cs index c079445559..a8dcfc3395 100644 --- a/test/Api.Test/AdminConsole/Controllers/SlackIntegrationControllerTests.cs +++ b/test/Api.Test/Dirt/Controllers/SlackIntegrationControllerTests.cs @@ -1,13 +1,13 @@ #nullable enable -using Bit.Api.AdminConsole.Controllers; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Api.Dirt.Controllers; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Mvc; @@ -16,7 +16,7 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Api.Test.AdminConsole.Controllers; +namespace Bit.Api.Test.Dirt.Controllers; [ControllerCustomize(typeof(SlackIntegrationController))] [SutProviderCustomize] diff --git a/test/Api.Test/AdminConsole/Controllers/TeamsIntegrationControllerTests.cs b/test/Api.Test/Dirt/Controllers/TeamsIntegrationControllerTests.cs similarity index 98% rename from test/Api.Test/AdminConsole/Controllers/TeamsIntegrationControllerTests.cs rename to test/Api.Test/Dirt/Controllers/TeamsIntegrationControllerTests.cs index 3302a87372..b7e778339b 100644 --- a/test/Api.Test/AdminConsole/Controllers/TeamsIntegrationControllerTests.cs +++ b/test/Api.Test/Dirt/Controllers/TeamsIntegrationControllerTests.cs @@ -1,14 +1,14 @@ #nullable enable -using Bit.Api.AdminConsole.Controllers; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Api.Dirt.Controllers; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Teams; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Exceptions; -using Bit.Core.Models.Teams; -using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; @@ -20,7 +20,7 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Api.Test.AdminConsole.Controllers; +namespace Bit.Api.Test.Dirt.Controllers; [ControllerCustomize(typeof(TeamsIntegrationController))] [SutProviderCustomize] diff --git a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationRequestModelTests.cs b/test/Api.Test/Dirt/Models/Request/OrganizationIntegrationRequestModelTests.cs similarity index 97% rename from test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationRequestModelTests.cs rename to test/Api.Test/Dirt/Models/Request/OrganizationIntegrationRequestModelTests.cs index 76e206abf4..190eae260c 100644 --- a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationRequestModelTests.cs +++ b/test/Api.Test/Dirt/Models/Request/OrganizationIntegrationRequestModelTests.cs @@ -1,13 +1,13 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Api.Dirt.Models.Request; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Api.Test.AdminConsole.Models.Request.Organizations; +namespace Bit.Api.Test.Dirt.Models.Request; public class OrganizationIntegrationRequestModelTests { diff --git a/test/Api.Test/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModelTests.cs b/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs similarity index 94% rename from test/Api.Test/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModelTests.cs rename to test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs index 28bc07de38..e6f8d5d756 100644 --- a/test/Api.Test/AdminConsole/Models/Response/Organizations/OrganizationIntegrationResponseModelTests.cs +++ b/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs @@ -1,15 +1,15 @@ #nullable enable using System.Text.Json; -using Bit.Api.AdminConsole.Models.Response.Organizations; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; -using Bit.Core.Models.Teams; +using Bit.Api.Dirt.Models.Response; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Teams; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Api.Test.AdminConsole.Models.Response.Organizations; +namespace Bit.Api.Test.Dirt.Models.Response; public class OrganizationIntegrationResponseModelTests { diff --git a/test/Core.Test/AdminConsole/Services/IntegrationTypeTests.cs b/test/Core.Test/AdminConsole/Services/IntegrationTypeTests.cs index 715bffaab1..134aa17129 100644 --- a/test/Core.Test/AdminConsole/Services/IntegrationTypeTests.cs +++ b/test/Core.Test/AdminConsole/Services/IntegrationTypeTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; using Xunit; namespace Bit.Core.Test.Services; diff --git a/test/Core.Test/AdminConsole/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs b/test/Core.Test/Dirt/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs similarity index 98% rename from test/Core.Test/AdminConsole/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs rename to test/Core.Test/Dirt/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs index 0ca2d55c78..37b303b735 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/EventIntegrationServiceCollectionExtensionsTests.cs @@ -1,12 +1,15 @@ -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.AdminConsole.Services; -using Bit.Core.AdminConsole.Services.NoopImplementations; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Dirt.Services.NoopImplementations; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Test.Dirt.Models.Data.EventIntegrations; using Bit.Core.Utilities; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; @@ -19,7 +22,7 @@ using StackExchange.Redis; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations; +namespace Bit.Core.Test.Dirt.EventIntegrations; public class EventIntegrationServiceCollectionExtensionsTests { diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs similarity index 96% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs index c6c8a44955..3ad3569c07 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/CreateOrganizationIntegrationConfigurationCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; -using Bit.Core.AdminConsole.Services; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -11,7 +12,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; [SutProviderCustomize] public class CreateOrganizationIntegrationConfigurationCommandTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs index 3b12f4bd88..c053a761bb 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/DeleteOrganizationIntegrationConfigurationCommandTests.cs @@ -1,8 +1,9 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -10,7 +11,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; [SutProviderCustomize] public class DeleteOrganizationIntegrationConfigurationCommandTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs similarity index 94% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs index 18541df53e..780467a91a 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/GetOrganizationIntegrationConfigurationsQueryTests.cs @@ -1,13 +1,13 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; [SutProviderCustomize] public class GetOrganizationIntegrationConfigurationsQueryTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs similarity index 98% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs index c2eeefc087..42ea278aa6 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrationConfigurations/UpdateOrganizationIntegrationConfigurationCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; -using Bit.Core.AdminConsole.Services; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -11,7 +12,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrationConfigurations; [SutProviderCustomize] public class UpdateOrganizationIntegrationConfigurationCommandTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs similarity index 93% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs index 62af1eb3ed..4933656eb3 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/CreateOrganizationIntegrationCommandTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrations; [SutProviderCustomize] public class CreateOrganizationIntegrationCommandTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs similarity index 92% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs index 25a00bded1..15a3b44bcf 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/DeleteOrganizationIntegrationCommandTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrations; [SutProviderCustomize] public class DeleteOrganizationIntegrationCommandTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs similarity index 86% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs index dfa8e4b306..19b35ac340 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/GetOrganizationIntegrationsQueryTests.cs @@ -1,12 +1,12 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; -using Bit.Core.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrations; [SutProviderCustomize] public class GetOrganizationIntegrationsQueryTests diff --git a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs similarity index 95% rename from test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs rename to test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs index fdedec2e51..34bf02c34b 100644 --- a/test/Core.Test/AdminConsole/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs +++ b/test/Core.Test/Dirt/EventIntegrations/OrganizationIntegrations/UpdateOrganizationIntegrationCommandTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrations; +namespace Bit.Core.Test.Dirt.EventIntegrations.OrganizationIntegrations; [SutProviderCustomize] public class UpdateOrganizationIntegrationCommandTests diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs similarity index 96% rename from test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs index 6925a978eb..4b6292b7c4 100644 --- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationHandlerResultTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class IntegrationHandlerResultTests { diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationMessageTests.cs similarity index 96% rename from test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationMessageTests.cs index 71f9a15037..6f0ce11db8 100644 --- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationMessageTests.cs @@ -1,9 +1,9 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Xunit; -namespace Bit.Core.Test.Models.Data.EventIntegrations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class IntegrationMessageTests { diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs similarity index 94% rename from test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs index 8605a3dcab..a3e05ffe37 100644 --- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationOAuthStateTests.cs @@ -1,12 +1,12 @@ #nullable enable -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Time.Testing; using Xunit; -namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class IntegrationOAuthStateTests { diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs index d9a3cd6e8a..7bacb4046b 100644 --- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/IntegrationTemplateContextTests.cs @@ -1,13 +1,13 @@ #nullable enable using System.Text.Json; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class IntegrationTemplateContextTests { diff --git a/test/Core.Test/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetailsTests.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetailsTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetailsTests.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetailsTests.cs index 4b8cd4f47c..ae574d7ee6 100644 --- a/test/Core.Test/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetailsTests.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/OrganizationIntegrationConfigurationDetailsTests.cs @@ -1,8 +1,8 @@ using System.Text.Json; -using Bit.Core.Models.Data.Organizations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; using Xunit; -namespace Bit.Core.Test.Models.Data.Organizations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class OrganizationIntegrationConfigurationDetailsTests { diff --git a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/TestListenerConfiguration.cs b/test/Core.Test/Dirt/Models/Data/EventIntegrations/TestListenerConfiguration.cs similarity index 86% rename from test/Core.Test/AdminConsole/Models/Data/EventIntegrations/TestListenerConfiguration.cs rename to test/Core.Test/Dirt/Models/Data/EventIntegrations/TestListenerConfiguration.cs index 50442dd463..2c811e06f5 100644 --- a/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/TestListenerConfiguration.cs +++ b/test/Core.Test/Dirt/Models/Data/EventIntegrations/TestListenerConfiguration.cs @@ -1,6 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; -namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; +namespace Bit.Core.Test.Dirt.Models.Data.EventIntegrations; public class TestListenerConfiguration : IIntegrationListenerConfiguration { diff --git a/test/Core.Test/AdminConsole/Models/Data/Teams/TeamsBotCredentialProviderTests.cs b/test/Core.Test/Dirt/Models/Data/Teams/TeamsBotCredentialProviderTests.cs similarity index 95% rename from test/Core.Test/AdminConsole/Models/Data/Teams/TeamsBotCredentialProviderTests.cs rename to test/Core.Test/Dirt/Models/Data/Teams/TeamsBotCredentialProviderTests.cs index d3d433727f..24576899d5 100644 --- a/test/Core.Test/AdminConsole/Models/Data/Teams/TeamsBotCredentialProviderTests.cs +++ b/test/Core.Test/Dirt/Models/Data/Teams/TeamsBotCredentialProviderTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.AdminConsole.Models.Teams; +using Bit.Core.Dirt.Models.Data.Teams; using Microsoft.Bot.Connector.Authentication; using Xunit; -namespace Bit.Core.Test.Models.Data.Teams; +namespace Bit.Core.Test.Dirt.Models.Data.Teams; public class TeamsBotCredentialProviderTests { diff --git a/test/Core.Test/AdminConsole/Services/AzureServiceBusEventListenerServiceTests.cs b/test/Core.Test/Dirt/Services/AzureServiceBusEventListenerServiceTests.cs similarity index 96% rename from test/Core.Test/AdminConsole/Services/AzureServiceBusEventListenerServiceTests.cs rename to test/Core.Test/Dirt/Services/AzureServiceBusEventListenerServiceTests.cs index c6ef3063e2..92f0b16b3f 100644 --- a/test/Core.Test/AdminConsole/Services/AzureServiceBusEventListenerServiceTests.cs +++ b/test/Core.Test/Dirt/Services/AzureServiceBusEventListenerServiceTests.cs @@ -2,9 +2,10 @@ using System.Text.Json; using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Core.Models.Data; -using Bit.Core.Services; +using Bit.Core.Test.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class AzureServiceBusEventListenerServiceTests diff --git a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs b/test/Core.Test/Dirt/Services/AzureServiceBusIntegrationListenerServiceTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs rename to test/Core.Test/Dirt/Services/AzureServiceBusIntegrationListenerServiceTests.cs index 9e46a3a99a..88688f49ff 100644 --- a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs +++ b/test/Core.Test/Dirt/Services/AzureServiceBusIntegrationListenerServiceTests.cs @@ -2,8 +2,10 @@ using System.Text.Json; using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Test.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Logging; @@ -11,7 +13,7 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class AzureServiceBusIntegrationListenerServiceTests diff --git a/test/Core.Test/AdminConsole/Services/DatadogIntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/DatadogIntegrationHandlerTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/DatadogIntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/DatadogIntegrationHandlerTests.cs index 9cb21f012a..a8c5d7da95 100644 --- a/test/Core.Test/AdminConsole/Services/DatadogIntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/DatadogIntegrationHandlerTests.cs @@ -1,8 +1,8 @@ #nullable enable using System.Net; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -11,7 +11,7 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class DatadogIntegrationHandlerTests diff --git a/test/Core.Test/AdminConsole/Services/EventIntegrationEventWriteServiceTests.cs b/test/Core.Test/Dirt/Services/EventIntegrationEventWriteServiceTests.cs similarity index 95% rename from test/Core.Test/AdminConsole/Services/EventIntegrationEventWriteServiceTests.cs rename to test/Core.Test/Dirt/Services/EventIntegrationEventWriteServiceTests.cs index 16df234004..3870601604 100644 --- a/test/Core.Test/AdminConsole/Services/EventIntegrationEventWriteServiceTests.cs +++ b/test/Core.Test/Dirt/Services/EventIntegrationEventWriteServiceTests.cs @@ -1,12 +1,13 @@ using System.Text.Json; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Core.Models.Data; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class EventIntegrationEventWriteServiceTests diff --git a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/EventIntegrationHandlerTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/EventIntegrationHandlerTests.cs index 235d597b12..e15a254b39 100644 --- a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/EventIntegrationHandlerTests.cs @@ -2,14 +2,15 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Core.Models.Data; -using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -19,7 +20,7 @@ using NSubstitute; using Xunit; using ZiggyCreatures.Caching.Fusion; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class EventIntegrationHandlerTests diff --git a/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs b/test/Core.Test/Dirt/Services/EventRepositoryHandlerTests.cs similarity index 90% rename from test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs rename to test/Core.Test/Dirt/Services/EventRepositoryHandlerTests.cs index 48c3a143d4..6392f0138d 100644 --- a/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/EventRepositoryHandlerTests.cs @@ -1,4 +1,5 @@ -using Bit.Core.Models.Data; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Models.Data; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -6,7 +7,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class EventRepositoryHandlerTests diff --git a/test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs b/test/Core.Test/Dirt/Services/IntegrationFilterFactoryTests.cs similarity index 91% rename from test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs rename to test/Core.Test/Dirt/Services/IntegrationFilterFactoryTests.cs index b408bc1501..83780b1fe0 100644 --- a/test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs +++ b/test/Core.Test/Dirt/Services/IntegrationFilterFactoryTests.cs @@ -1,9 +1,9 @@ -using Bit.Core.Models.Data; -using Bit.Core.Services; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Models.Data; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; public class IntegrationFilterFactoryTests { diff --git a/test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs b/test/Core.Test/Dirt/Services/IntegrationFilterServiceTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs rename to test/Core.Test/Dirt/Services/IntegrationFilterServiceTests.cs index fb33737c16..b7510b0e92 100644 --- a/test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs +++ b/test/Core.Test/Dirt/Services/IntegrationFilterServiceTests.cs @@ -1,13 +1,13 @@ #nullable enable using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services.Implementations; using Bit.Core.Models.Data; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; public class IntegrationFilterServiceTests { diff --git a/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/IntegrationHandlerTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/IntegrationHandlerTests.cs index b3bbcb7ef2..096fcc11bb 100644 --- a/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/IntegrationHandlerTests.cs @@ -1,10 +1,10 @@ using System.Net; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Enums; -using Bit.Core.Services; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; public class IntegrationHandlerTests { diff --git a/test/Core.Test/AdminConsole/Services/OrganizationIntegrationConfigurationValidatorTests.cs b/test/Core.Test/Dirt/Services/OrganizationIntegrationConfigurationValidatorTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/OrganizationIntegrationConfigurationValidatorTests.cs rename to test/Core.Test/Dirt/Services/OrganizationIntegrationConfigurationValidatorTests.cs index 1154ad8025..bee6a5182c 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationIntegrationConfigurationValidatorTests.cs +++ b/test/Core.Test/Dirt/Services/OrganizationIntegrationConfigurationValidatorTests.cs @@ -1,11 +1,11 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.AdminConsole.Services; -using Bit.Core.Enums; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Enums; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services.Implementations; using Xunit; -namespace Bit.Core.Test.AdminConsole.Services; +namespace Bit.Core.Test.Dirt.Services; public class OrganizationIntegrationConfigurationValidatorTests { diff --git a/test/Core.Test/AdminConsole/Services/RabbitMqEventListenerServiceTests.cs b/test/Core.Test/Dirt/Services/RabbitMqEventListenerServiceTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/RabbitMqEventListenerServiceTests.cs rename to test/Core.Test/Dirt/Services/RabbitMqEventListenerServiceTests.cs index 22e297a00d..560cf589ed 100644 --- a/test/Core.Test/AdminConsole/Services/RabbitMqEventListenerServiceTests.cs +++ b/test/Core.Test/Dirt/Services/RabbitMqEventListenerServiceTests.cs @@ -1,9 +1,10 @@ #nullable enable using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Core.Models.Data; -using Bit.Core.Services; +using Bit.Core.Test.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -13,7 +14,7 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class RabbitMqEventListenerServiceTests diff --git a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs b/test/Core.Test/Dirt/Services/RabbitMqIntegrationListenerServiceTests.cs similarity index 98% rename from test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs rename to test/Core.Test/Dirt/Services/RabbitMqIntegrationListenerServiceTests.cs index 71985889f8..453a4e6527 100644 --- a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs +++ b/test/Core.Test/Dirt/Services/RabbitMqIntegrationListenerServiceTests.cs @@ -1,8 +1,10 @@ #nullable enable using System.Text; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; +using Bit.Core.Test.Dirt.Models.Data.EventIntegrations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -13,7 +15,7 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class RabbitMqIntegrationListenerServiceTests diff --git a/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/SlackIntegrationHandlerTests.cs similarity index 96% rename from test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/SlackIntegrationHandlerTests.cs index e455100995..52bb7a03a4 100644 --- a/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/SlackIntegrationHandlerTests.cs @@ -1,13 +1,14 @@ -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Models.Slack; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Slack; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class SlackIntegrationHandlerTests diff --git a/test/Core.Test/AdminConsole/Services/SlackServiceTests.cs b/test/Core.Test/Dirt/Services/SlackServiceTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/Services/SlackServiceTests.cs rename to test/Core.Test/Dirt/Services/SlackServiceTests.cs index 068e5e8c82..bbb505f5d3 100644 --- a/test/Core.Test/AdminConsole/Services/SlackServiceTests.cs +++ b/test/Core.Test/Dirt/Services/SlackServiceTests.cs @@ -3,7 +3,7 @@ using System.Net; using System.Text.Json; using System.Web; -using Bit.Core.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.MockedHttpClient; @@ -11,7 +11,7 @@ using NSubstitute; using Xunit; using GlobalSettings = Bit.Core.Settings.GlobalSettings; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class SlackServiceTests diff --git a/test/Core.Test/AdminConsole/Services/TeamsIntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/TeamsIntegrationHandlerTests.cs similarity index 98% rename from test/Core.Test/AdminConsole/Services/TeamsIntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/TeamsIntegrationHandlerTests.cs index 11056ec2cc..b608ed7ff8 100644 --- a/test/Core.Test/AdminConsole/Services/TeamsIntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/TeamsIntegrationHandlerTests.cs @@ -1,6 +1,7 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -9,7 +10,7 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class TeamsIntegrationHandlerTests diff --git a/test/Core.Test/AdminConsole/Services/TeamsServiceTests.cs b/test/Core.Test/Dirt/Services/TeamsServiceTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/Services/TeamsServiceTests.cs rename to test/Core.Test/Dirt/Services/TeamsServiceTests.cs index 17d65f3237..61d20cc0af 100644 --- a/test/Core.Test/AdminConsole/Services/TeamsServiceTests.cs +++ b/test/Core.Test/Dirt/Services/TeamsServiceTests.cs @@ -3,11 +3,11 @@ using System.Net; using System.Text.Json; using System.Web; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Models.Teams; -using Bit.Core.Repositories; -using Bit.Core.Services; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Models.Data.Teams; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.MockedHttpClient; @@ -15,7 +15,7 @@ using NSubstitute; using Xunit; using GlobalSettings = Bit.Core.Settings.GlobalSettings; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class TeamsServiceTests diff --git a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs b/test/Core.Test/Dirt/Services/WebhookIntegrationHandlerTests.cs similarity index 98% rename from test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs rename to test/Core.Test/Dirt/Services/WebhookIntegrationHandlerTests.cs index 05aa46681a..5d8bbfe439 100644 --- a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs +++ b/test/Core.Test/Dirt/Services/WebhookIntegrationHandlerTests.cs @@ -1,7 +1,7 @@ using System.Net; using System.Net.Http.Headers; -using Bit.Core.AdminConsole.Models.Data.EventIntegrations; -using Bit.Core.Services; +using Bit.Core.Dirt.Models.Data.EventIntegrations; +using Bit.Core.Dirt.Services.Implementations; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -10,7 +10,7 @@ using Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.Dirt.Services; [SutProviderCustomize] public class WebhookIntegrationHandlerTests diff --git a/test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs b/test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs index a87392c2c1..7b467c0af4 100644 --- a/test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs +++ b/test/Core.Test/Utilities/EventIntegrationsCacheConstantsTests.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using Bit.Core.Dirt.Enums; +using Bit.Core.Enums; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; From c98f31a9f7b6ded57d6f74d7dd37fff52eb9a807 Mon Sep 17 00:00:00 2001 From: Mick Letofsky Date: Tue, 30 Dec 2025 18:22:09 +0100 Subject: [PATCH 05/58] Review Code Triggered by labeled event (#6782) --- .github/workflows/review-code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-code.yml b/.github/workflows/review-code.yml index 0e0597fccf..908664209d 100644 --- a/.github/workflows/review-code.yml +++ b/.github/workflows/review-code.yml @@ -2,7 +2,7 @@ name: Code Review on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, labeled] permissions: {} From f82552fba93a9a68f7cf71f2d54f72d6bfce4ad1 Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:08:10 -0500 Subject: [PATCH 06/58] [PM-29568] Fix footer styling (#6722) * fix: update footer background color to match UIF Tailwind standards. * fix: modify spacing for footer * chore: build templates * fix: update social icon assets * fix: update footer image source * fix: update send access --- .../Auth/SendAccessEmailOtpEmailv2.html.hbs | 68 ++++----- ...ion-confirmation-enterprise-teams.html.hbs | 68 ++++----- ...nization-confirmation-family-free.html.hbs | 68 ++++----- .../Onboarding/welcome-family-user.html.hbs | 142 ++++++++---------- .../welcome-individual-user.html.hbs | 142 ++++++++---------- .../Auth/Onboarding/welcome-org-user.html.hbs | 142 ++++++++---------- .../MailTemplates/Mjml/components/footer.mjml | 20 +-- .../components/mj-bw-learn-more-footer.js | 2 +- 8 files changed, 302 insertions(+), 350 deletions(-) diff --git a/src/Core/MailTemplates/Handlebars/Auth/SendAccessEmailOtpEmailv2.html.hbs b/src/Core/MailTemplates/Handlebars/Auth/SendAccessEmailOtpEmailv2.html.hbs index f9cc04f73e..352bb447c8 100644 --- a/src/Core/MailTemplates/Handlebars/Auth/SendAccessEmailOtpEmailv2.html.hbs +++ b/src/Core/MailTemplates/Handlebars/Auth/SendAccessEmailOtpEmailv2.html.hbs @@ -378,12 +378,12 @@ - + -
+
- +
@@ -471,8 +471,8 @@ - -
- + +
@@ -488,13 +488,13 @@
- +
+ - @@ -511,13 +511,13 @@ -
+ - +
- +
+ - @@ -534,13 +534,13 @@ -
+ - +
- +
+ - @@ -557,13 +557,13 @@ -
+ - +
- +
+ - @@ -580,13 +580,13 @@ -
+ - +
- +
+ - @@ -603,13 +603,13 @@ -
+ - +
- +
+ - @@ -626,13 +626,13 @@ -
+ - +
- +
+ - @@ -653,7 +653,7 @@
+ - +
-

+

© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa Barbara, CA, USA

diff --git a/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.html.hbs b/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.html.hbs index 65e37e87dd..be1a3854b5 100644 --- a/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.html.hbs +++ b/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.html.hbs @@ -502,12 +502,12 @@
- + -
+
- +
@@ -595,8 +595,8 @@ - -
- + +
@@ -612,13 +612,13 @@
- +
+ - @@ -635,13 +635,13 @@ -
+ - +
- +
+ - @@ -658,13 +658,13 @@ -
+ - +
- +
+ - @@ -681,13 +681,13 @@ -
+ - +
- +
+ - @@ -704,13 +704,13 @@ -
+ - +
- +
+ - @@ -727,13 +727,13 @@ -
+ - +
- +
+ - @@ -750,13 +750,13 @@ -
+ - +
- +
+ - @@ -777,7 +777,7 @@
+ - +
-

+

© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa Barbara, CA, USA

diff --git a/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.html.hbs b/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.html.hbs index c22bc80a51..b9984343d5 100644 --- a/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.html.hbs +++ b/src/Core/MailTemplates/Handlebars/MJML/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.html.hbs @@ -670,12 +670,12 @@
- + -
+
- +
@@ -763,8 +763,8 @@ - -
- + +
@@ -780,13 +780,13 @@
- +
+ - @@ -803,13 +803,13 @@ -
+ - +
- +
+ - @@ -826,13 +826,13 @@ -
+ - +
- +
+ - @@ -849,13 +849,13 @@ -
+ - +
- +
+ - @@ -872,13 +872,13 @@ -
+ - +
- +
+ - @@ -895,13 +895,13 @@ -
+ - +
- +
+ - @@ -918,13 +918,13 @@ -
+ - +
- +
+ - @@ -945,7 +945,7 @@
+ - +
-

+

© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa Barbara, CA, USA

diff --git a/src/Core/MailTemplates/Handlebars/MJML/Auth/Onboarding/welcome-family-user.html.hbs b/src/Core/MailTemplates/Handlebars/MJML/Auth/Onboarding/welcome-family-user.html.hbs index 9c4b2406d4..1998cf10ba 100644 --- a/src/Core/MailTemplates/Handlebars/MJML/Auth/Onboarding/welcome-family-user.html.hbs +++ b/src/Core/MailTemplates/Handlebars/MJML/Auth/Onboarding/welcome-family-user.html.hbs @@ -30,6 +30,14 @@ + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - + + + + - + - - - - - -
- - - - - -
- + + + + + +
+ + + + + +
+ - + - - - -
- - - - - - - - -
- - - - - -
- - - - - - +
- - -
- - - - - - - - - - - - - - - - - -
- - - - - - - -
- - - -
- +
+ + + + + + + + +
+ + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+ +
+ +

+ You can now share passwords with members of {{OrganizationName}}! +

+ +
+ + + + + + + +
+ + Log in + +
+ +
+ +
+ + + +
+ + + + + + + + + +
+ + + + + + + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + +
+ +
- -

- You can now share passwords with members of {{OrganizationName}}! -

- -
- - - - - - - -
- - Log in - -
- -
- -
- - - -
- - - - - - - - - -
- - - - - - - -
- - - -
- -
- -
- - -
- -
- - - - - -
- - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - - -
- - - -
- - - - - - - -
- - -
- - - - - - - - - -
- -
As a member of {{OrganizationName}}:
- -
- -
- - -
- -
- - - - - -
- - - - - - - -
- - -
- - -
- - - - - - - - - -
- - - - - - - -
- - Organization Icon - -
- -
- -
- - - -
- - - - - - - - - -
- -
Your account is owned by {{OrganizationName}} and is subject to their security and management policies.
- -
- -
- - -
- - -
- -
- - - - - -
- - - - - - +
- - -
- - -
- - - - - - - - - -
- - - - - - - -
- - Group Users Icon - -
- -
- -
- - - -
- - - - - - - - - - - - - -
- -
You can easily access and share passwords with your team.
- -
- - + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ +
As a member of {{OrganizationName}}:
+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + Organization Icon + +
+ +
+ +
+ + + +
+ + + + + + + + + +
+ +
Your account is owned by {{OrganizationName}} and is subject to their security and management policies.
+ +
+ +
+ + +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + Group Users Icon + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + +
+ +
You can easily access and share passwords with your team.
+ +
+ + - + + +
+ +
+ + +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + +
- -
- - -
- - -
- -
- - - - - -
- - - - - - - -
- -
- -
- - - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - - -
- - - -
- - - - - - +
- - -
- - - - - - - - - -
- -

- Learn more about Bitwarden -

- Find user guides, product documentation, and videos on the - Bitwarden Help Center.
- +
+ + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ +

+ Learn more about Bitwarden +

+ Find user guides, product documentation, and videos on the + Bitwarden Help Center.
+ +
+ +
+ + + +
+ + + + + + + + + +
+ +
+ + +
+ +
+ + +
- -
- - - -
- - - - - - - - - -
- -
- - -
- -
- - - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - +
- - -
- - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - - - - -
- - - + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + +
+ +

+ © 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa + Barbara, CA, USA +

+

+ Always confirm you are on a trusted Bitwarden domain before logging + in:
+ bitwarden.com | + Learn why we include this +

+ +
+ +
+ +
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - -
- -

- © 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa - Barbara, CA, USA -

-

- Always confirm you are on a trusted Bitwarden domain before logging - in:
- bitwarden.com | - Learn why we include this -

- -
- -
- - -
- -
- - - - - -
- - + +
+ + + + + +
+ + - \ No newline at end of file diff --git a/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationConfirmation/OrganizationConfirmationFamilyFreeView.html.hbs b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationConfirmation/OrganizationConfirmationFamilyFreeView.html.hbs index cbe09d3e93..c0f838e0c7 100644 --- a/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationConfirmation/OrganizationConfirmationFamilyFreeView.html.hbs +++ b/src/Core/AdminConsole/Models/Mail/Mailer/OrganizationConfirmation/OrganizationConfirmationFamilyFreeView.html.hbs @@ -1,6 +1,6 @@ - + @@ -8,976 +8,976 @@ - - - - - - - + + + + + + + - - - - + + + + - + - - - - - -
- - - - - -
- + + + + + +
+ + + + + +
+ - + - - - -
- - - - - - - - -
- - - - - -
- - - - - - +
- - -
- - - - - - - - - - - - - - - - - -
- - - - - - - -
- - - -
- +
+ + + + + + + + +
+ + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + +
+ +
+ +

+ You can now share passwords with members of {{OrganizationName}}! +

+ +
+ + + + + + + +
+ + Log in + +
+ +
+ +
+ + + +
+ + + + + + + + + +
+ + + + + + + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + +
+ +
- -

- You can now share passwords with members of {{OrganizationName}}! -

- -
- - - - - - - -
- - Log in - -
- -
- -
- - - -
- - - - - - - - - -
- - - - - - - -
- - - -
- -
- -
- - -
- -
- - - - - -
- - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - - -
- - - -
- - - - - - - -
- - -
- - - - - - - - - -
- -
As a member of {{OrganizationName}}:
- -
- -
- - -
- -
- - - - - -
- - - - - - - -
- - -
- - -
- - - - - - - - - -
- - - - - - - -
- - Collections Icon - -
- -
- -
- - - -
- - - - - - - - - -
- -
You can access passwords {{OrganizationName}} has shared with you.
- -
- -
- - -
- - -
- -
- - - - - -
- - - - - - +
- - -
- - -
- - - - - - - - - -
- - - - - - - -
- - Group Users Icon - -
- -
- -
- - - -
- - - - - - - - - - - - - -
- -
You can easily share passwords with friends, family, or coworkers.
- -
- - + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ +
As a member of {{OrganizationName}}:
+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + Collections Icon + +
+ +
+ +
+ + + +
+ + + + + + + + + +
+ +
You can access passwords {{OrganizationName}} has shared with you.
+ +
+ +
+ + +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + Group Users Icon + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + +
+ +
You can easily share passwords with friends, family, or coworkers.
+ +
+ + - + + +
+ +
+ + +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ +
+ +
+ + +
- -
- - -
- - -
- -
- - - - - -
- - - - - - - -
- -
- -
- - - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - - -
- - - -
- - - - - - +
- - -
- - - - - - - - - - - - - -
- -
Download Bitwarden on all devices
- +
+ + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + +
+ +
Download Bitwarden on all devices
+ +
+ +
Already using the browser extension? + Download the Bitwarden mobile app from the + App Store + or Google Play + to quickly save logins and autofill forms on the go.
+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + + + + + +
+ + + + Download on the App Store + + + +
+ +
+ +
+ + + +
+ + + + + + + + + +
+ + + + + + + +
+ + + + Get it on Google Play + + + +
+ +
+ +
+ + +
+ + +
+ +
+ + +
- -
Already using the browser extension? - Download the Bitwarden mobile app from the - App Store - or Google Play - to quickly save logins and autofill forms on the go.
- -
- -
- - -
- -
- - - - - -
- - - - - - - -
- - -
- - -
- - - - - - - - - -
- - - - - - - -
- - - - Download on the App Store - - - -
- -
- -
- - - -
- - - - - - - - - -
- - - - - - - -
- - - - Get it on Google Play - - - -
- -
- -
- - -
- - -
- -
- - - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - - -
- - - -
- - - - - - +
- - -
- - - - - - - - - -
- -

- Learn more about Bitwarden -

- Find user guides, product documentation, and videos on the - Bitwarden Help Center.
- +
+ + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ +

+ Learn more about Bitwarden +

+ Find user guides, product documentation, and videos on the + Bitwarden Help Center.
+ +
+ +
+ + + +
+ + + + + + + + + +
+ +
+ + +
+ +
+ + +
- -
- - - -
- - - - - - - - - -
- -
- - -
- -
- - - -
- -
- - - - + +
+ + + + - - - - -
- + + + + +
+ - + - - +
- - -
- - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - - - - -
- - - + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ + + + + + +
+ + + +
+
+ + + +
+ +

+ © 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa + Barbara, CA, USA +

+

+ Always confirm you are on a trusted Bitwarden domain before logging + in:
+ bitwarden.com | + Learn why we include this +

+ +
+ +
+ +
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - - - - - - - - -
- - - - - - -
- - - -
-
- - - -
- -

- © 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa - Barbara, CA, USA -

-

- Always confirm you are on a trusted Bitwarden domain before logging - in:
- bitwarden.com | - Learn why we include this -

- -
- -
- - -
- -
- - - - - -
- - + +
+ + + + + +
+ + - \ No newline at end of file + diff --git a/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.mjml b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.mjml index 6d3c46ae67..b94bf0dc86 100644 --- a/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.mjml +++ b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-enterprise-teams.mjml @@ -8,8 +8,8 @@ @@ -33,7 +33,7 @@ icon-alt="Group Users Icon" text="You can easily access and share passwords with your team." foot-url-text="Share passwords in Bitwarden" - foot-url="https://bitwarden.com/help/share-to-a-collection/" + foot-url="https://bitwarden.com/help/sharing" /> diff --git a/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.mjml b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.mjml index 2b2d854134..c223e2f650 100644 --- a/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.mjml +++ b/src/Core/MailTemplates/Mjml/emails/AdminConsole/OrganizationConfirmation/organization-confirmation-family-free.mjml @@ -8,8 +8,8 @@ @@ -33,7 +33,7 @@ icon-alt="Group Users Icon" text="You can easily share passwords with friends, family, or coworkers." foot-url-text="Share passwords in Bitwarden" - foot-url="https://bitwarden.com/help/share-to-a-collection/" + foot-url="https://bitwarden.com/help/sharing" /> From 12d18ebb2c749a958f8f5305f26176527b322008 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:32:02 -0500 Subject: [PATCH 43/58] [PM-27731] Updated organization licenses to save the correct values from the token (#6546) * Updated organization licenses to save the correct values from the token * Added additional test cases around licenses * Added missing properties from Organization to UpdateOrganizationLicenseCommand.UpdateLicenseAsync() * Add tests to validate license property synchronization pipeline * `dotnet format` --- .../SelfHostedOrganizationDetails.cs | 3 + .../UpdateOrganizationLicenseCommand.cs | 59 +++- .../Services/Implementations/UserService.cs | 12 + .../Entities/OrganizationTests.cs | 106 +++++- .../Billing/Licenses/LicenseConstantsTests.cs | 68 ++++ .../OrganizationLicenseClaimsFactoryTests.cs | 92 +++++ .../UpdateOrganizationLicenseCommandTests.cs | 321 +++++++++++++++++- 7 files changed, 653 insertions(+), 8 deletions(-) create mode 100644 test/Core.Test/Billing/Licenses/LicenseConstantsTests.cs create mode 100644 test/Core.Test/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactoryTests.cs diff --git a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs index d74fb4f138..5ec9dc255a 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs @@ -128,6 +128,7 @@ public class SelfHostedOrganizationDetails : Organization UseApi = UseApi, UseResetPassword = UseResetPassword, UseSecretsManager = UseSecretsManager, + UsePasswordManager = UsePasswordManager, SelfHost = SelfHost, UsersGetPremium = UsersGetPremium, UseCustomPermissions = UseCustomPermissions, @@ -156,6 +157,8 @@ public class SelfHostedOrganizationDetails : Organization UseAdminSponsoredFamilies = UseAdminSponsoredFamilies, UseDisableSmAdsForUsers = UseDisableSmAdsForUsers, UsePhishingBlocker = UsePhishingBlocker, + UseOrganizationDomains = UseOrganizationDomains, + UseAutomaticUserConfirmation = UseAutomaticUserConfirmation, }; } } diff --git a/src/Core/Billing/Organizations/Commands/UpdateOrganizationLicenseCommand.cs b/src/Core/Billing/Organizations/Commands/UpdateOrganizationLicenseCommand.cs index 1dfd786210..000edda1c2 100644 --- a/src/Core/Billing/Organizations/Commands/UpdateOrganizationLicenseCommand.cs +++ b/src/Core/Billing/Organizations/Commands/UpdateOrganizationLicenseCommand.cs @@ -1,9 +1,11 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Enums; using Bit.Core.Billing.Licenses; using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Services; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations; using Bit.Core.Services; @@ -46,6 +48,57 @@ public class UpdateOrganizationLicenseCommand : IUpdateOrganizationLicenseComman } var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license); + + // If the license has a Token (claims-based), extract all properties from claims BEFORE validation + // This ensures that CanUseLicense validation has access to the correct values from claims + // Otherwise, fall back to using the properties already on the license object (backward compatibility) + if (claimsPrincipal != null) + { + license.Name = claimsPrincipal.GetValue(OrganizationLicenseConstants.Name); + license.BillingEmail = claimsPrincipal.GetValue(OrganizationLicenseConstants.BillingEmail); + license.BusinessName = claimsPrincipal.GetValue(OrganizationLicenseConstants.BusinessName); + license.PlanType = claimsPrincipal.GetValue(OrganizationLicenseConstants.PlanType); + license.Seats = claimsPrincipal.GetValue(OrganizationLicenseConstants.Seats); + license.MaxCollections = claimsPrincipal.GetValue(OrganizationLicenseConstants.MaxCollections); + license.UsePolicies = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePolicies); + license.UseSso = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSso); + license.UseKeyConnector = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseKeyConnector); + license.UseScim = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseScim); + license.UseGroups = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseGroups); + license.UseDirectory = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseDirectory); + license.UseEvents = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseEvents); + license.UseTotp = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseTotp); + license.Use2fa = claimsPrincipal.GetValue(OrganizationLicenseConstants.Use2fa); + license.UseApi = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseApi); + license.UseResetPassword = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseResetPassword); + license.Plan = claimsPrincipal.GetValue(OrganizationLicenseConstants.Plan); + license.SelfHost = claimsPrincipal.GetValue(OrganizationLicenseConstants.SelfHost); + license.UsersGetPremium = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsersGetPremium); + license.UseCustomPermissions = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseCustomPermissions); + license.Enabled = claimsPrincipal.GetValue(OrganizationLicenseConstants.Enabled); + license.Expires = claimsPrincipal.GetValue(OrganizationLicenseConstants.Expires); + license.LicenseKey = claimsPrincipal.GetValue(OrganizationLicenseConstants.LicenseKey); + license.UsePasswordManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePasswordManager); + license.UseSecretsManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSecretsManager); + license.SmSeats = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmSeats); + license.SmServiceAccounts = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmServiceAccounts); + license.UseRiskInsights = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseRiskInsights); + license.UseOrganizationDomains = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseOrganizationDomains); + license.UseAdminSponsoredFamilies = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseAdminSponsoredFamilies); + license.UseAutomaticUserConfirmation = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseAutomaticUserConfirmation); + license.UseDisableSmAdsForUsers = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseDisableSmAdsForUsers); + license.UsePhishingBlocker = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePhishingBlocker); + license.MaxStorageGb = claimsPrincipal.GetValue(OrganizationLicenseConstants.MaxStorageGb); + license.InstallationId = claimsPrincipal.GetValue(OrganizationLicenseConstants.InstallationId); + license.LicenseType = claimsPrincipal.GetValue(OrganizationLicenseConstants.LicenseType); + license.Issued = claimsPrincipal.GetValue(OrganizationLicenseConstants.Issued); + license.Refresh = claimsPrincipal.GetValue(OrganizationLicenseConstants.Refresh); + license.ExpirationWithoutGracePeriod = claimsPrincipal.GetValue(OrganizationLicenseConstants.ExpirationWithoutGracePeriod); + license.Trial = claimsPrincipal.GetValue(OrganizationLicenseConstants.Trial); + license.LimitCollectionCreationDeletion = claimsPrincipal.GetValue(OrganizationLicenseConstants.LimitCollectionCreationDeletion); + license.AllowAdminAccessToAllCollectionItems = claimsPrincipal.GetValue(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems); + } + var canUse = license.CanUse(_globalSettings, _licensingService, claimsPrincipal, out var exception) && selfHostedOrganization.CanUseLicense(license, out exception); @@ -54,12 +107,6 @@ public class UpdateOrganizationLicenseCommand : IUpdateOrganizationLicenseComman throw new BadRequestException(exception); } - var useAutomaticUserConfirmation = claimsPrincipal? - .GetValue(OrganizationLicenseConstants.UseAutomaticUserConfirmation) ?? false; - - selfHostedOrganization.UseAutomaticUserConfirmation = useAutomaticUserConfirmation; - license.UseAutomaticUserConfirmation = useAutomaticUserConfirmation; - await WriteLicenseFileAsync(selfHostedOrganization, license); await UpdateOrganizationAsync(selfHostedOrganization, license); } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 763f70dd0c..64caf1d462 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -14,6 +14,8 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Billing.Licenses; +using Bit.Core.Billing.Licenses.Extensions; using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Business; using Bit.Core.Billing.Models.Sales; @@ -982,6 +984,16 @@ public class UserService : UserManager, IUserService throw new BadRequestException(exceptionMessage); } + // If the license has a Token (claims-based), extract all properties from claims + // Otherwise, fall back to using the properties already on the license object (backward compatibility) + if (claimsPrincipal != null) + { + license.LicenseKey = claimsPrincipal.GetValue(UserLicenseConstants.LicenseKey); + license.Premium = claimsPrincipal.GetValue(UserLicenseConstants.Premium); + license.MaxStorageGb = claimsPrincipal.GetValue(UserLicenseConstants.MaxStorageGb); + license.Expires = claimsPrincipal.GetValue(UserLicenseConstants.Expires); + } + var dir = $"{_globalSettings.LicenseDirectory}/user"; Directory.CreateDirectory(dir); using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json")); diff --git a/test/Core.Test/AdminConsole/Entities/OrganizationTests.cs b/test/Core.Test/AdminConsole/Entities/OrganizationTests.cs index 7fcda324d9..aa0c2c80c3 100644 --- a/test/Core.Test/AdminConsole/Entities/OrganizationTests.cs +++ b/test/Core.Test/AdminConsole/Entities/OrganizationTests.cs @@ -1,7 +1,10 @@ -using System.Text.Json; +using System.Reflection; +using System.Text.Json; +using System.Text.RegularExpressions; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; +using Bit.Core.Billing.Organizations.Models; using Bit.Test.Common.Helpers; using Xunit; @@ -115,4 +118,105 @@ public class OrganizationTests Assert.True(organization.UseDisableSmAdsForUsers); } + + [Fact] + public void UpdateFromLicense_AppliesAllLicenseProperties() + { + // This test ensures that when a new property is added to OrganizationLicense, + // it is also applied to the Organization in UpdateFromLicense(). + // This is the fourth step in the license synchronization pipeline: + // Property → Constant → Claim → Extraction → Application + + // 1. Get all public properties from OrganizationLicense + var licenseProperties = typeof(OrganizationLicense) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => p.Name) + .ToHashSet(); + + // 2. Define properties that don't need to be applied to Organization + var excludedProperties = new HashSet + { + // Internal/computed properties + "SignatureBytes", // Computed from Signature property + "ValidLicenseVersion", // Internal property, not serialized + "CurrentLicenseFileVersion", // Constant field, not an instance property + "Hash", // Signature-related, not applied to org + "Signature", // Signature-related, not applied to org + "Token", // The JWT itself, not applied to org + "Version", // License version, not stored on org + + // Properties intentionally excluded from UpdateFromLicense + "Id", // Self-hosted org has its own unique Guid + "MaxStorageGb", // Not enforced for self-hosted (per comment in UpdateFromLicense) + + // Properties not stored on Organization model + "LicenseType", // Not a property on Organization + "InstallationId", // Not a property on Organization + "Issued", // Not a property on Organization + "Refresh", // Not a property on Organization + "ExpirationWithoutGracePeriod", // Not a property on Organization + "Trial", // Not a property on Organization + "Expires", // Mapped to ExpirationDate on Organization (different name) + + // Deprecated properties not applied + "LimitCollectionCreationDeletion", // Deprecated, not applied + "AllowAdminAccessToAllCollectionItems", // Deprecated, not applied + }; + + // 3. Get properties that should be applied + var propertiesThatShouldBeApplied = licenseProperties + .Except(excludedProperties) + .ToHashSet(); + + // 4. Read Organization.UpdateFromLicense source code + var organizationSourcePath = Path.Combine( + Directory.GetCurrentDirectory(), + "..", "..", "..", "..", "..", "src", "Core", "AdminConsole", "Entities", "Organization.cs"); + var sourceCode = File.ReadAllText(organizationSourcePath); + + // 5. Find all property assignments in UpdateFromLicense method + // Pattern matches: PropertyName = license.PropertyName + // This regex looks for assignments like "Name = license.Name" or "ExpirationDate = license.Expires" + var assignmentPattern = @"(\w+)\s*=\s*license\.(\w+)"; + var matches = Regex.Matches(sourceCode, assignmentPattern); + + var appliedProperties = new HashSet(); + foreach (Match match in matches) + { + // Get the license property name (right side of assignment) + var licensePropertyName = match.Groups[2].Value; + appliedProperties.Add(licensePropertyName); + } + + // Special case: Expires is mapped to ExpirationDate + if (appliedProperties.Contains("Expires")) + { + appliedProperties.Add("Expires"); // Already added, but being explicit + } + + // 6. Find missing applications + var missingApplications = propertiesThatShouldBeApplied + .Except(appliedProperties) + .OrderBy(p => p) + .ToList(); + + // 7. Build error message with guidance + var errorMessage = ""; + if (missingApplications.Any()) + { + errorMessage = $"The following OrganizationLicense properties are NOT applied to Organization in UpdateFromLicense():\n"; + errorMessage += string.Join("\n", missingApplications.Select(p => $" - {p}")); + errorMessage += "\n\nPlease add the following lines to Organization.UpdateFromLicense():\n"; + foreach (var prop in missingApplications) + { + errorMessage += $" {prop} = license.{prop};\n"; + } + errorMessage += "\nNote: If the property maps to a different name on Organization (like Expires → ExpirationDate), adjust accordingly."; + } + + // 8. Assert - if this fails, the error message guides the developer to add the application + Assert.True( + !missingApplications.Any(), + $"\n{errorMessage}"); + } } diff --git a/test/Core.Test/Billing/Licenses/LicenseConstantsTests.cs b/test/Core.Test/Billing/Licenses/LicenseConstantsTests.cs new file mode 100644 index 0000000000..df0e7133ad --- /dev/null +++ b/test/Core.Test/Billing/Licenses/LicenseConstantsTests.cs @@ -0,0 +1,68 @@ +using System.Reflection; +using Bit.Core.Billing.Licenses; +using Bit.Core.Billing.Organizations.Models; +using Xunit; + +namespace Bit.Core.Test.Billing.Licenses; + +public class LicenseConstantsTests +{ + [Fact] + public void OrganizationLicenseConstants_HasConstantForEveryLicenseProperty() + { + // This test ensures that when a new property is added to OrganizationLicense, + // a corresponding constant is added to OrganizationLicenseConstants. + // This is the first step in the license synchronization pipeline: + // Property → Constant → Claim → Extraction → Application + + // 1. Get all public properties from OrganizationLicense + var licenseProperties = typeof(OrganizationLicense) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => p.Name) + .ToHashSet(); + + // 2. Get all constants from OrganizationLicenseConstants + var constants = typeof(OrganizationLicenseConstants) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => f.IsLiteral && !f.IsInitOnly) + .Select(f => f.GetValue(null) as string) + .ToHashSet(); + + // 3. Define properties that don't need constants (internal/computed/non-claims properties) + var excludedProperties = new HashSet + { + "SignatureBytes", // Computed from Signature property + "ValidLicenseVersion", // Internal property, not serialized + "CurrentLicenseFileVersion", // Constant field, not an instance property + "Hash", // Signature-related, not in claims system + "Signature", // Signature-related, not in claims system + "Token", // The JWT itself, not a claim within the token + "Version" // Not in claims system (only in deprecated property-based licenses) + }; + + // 4. Find license properties without corresponding constants + var propertiesWithoutConstants = licenseProperties + .Except(constants) + .Except(excludedProperties) + .OrderBy(p => p) + .ToList(); + + // 5. Build error message with guidance + var errorMessage = ""; + if (propertiesWithoutConstants.Any()) + { + errorMessage = $"The following OrganizationLicense properties don't have constants in OrganizationLicenseConstants:\n"; + errorMessage += string.Join("\n", propertiesWithoutConstants.Select(p => $" - {p}")); + errorMessage += "\n\nPlease add the following constants to OrganizationLicenseConstants:\n"; + foreach (var prop in propertiesWithoutConstants) + { + errorMessage += $" public const string {prop} = nameof({prop});\n"; + } + } + + // 6. Assert - if this fails, the error message guides the developer to add the constant + Assert.True( + !propertiesWithoutConstants.Any(), + $"\n{errorMessage}"); + } +} diff --git a/test/Core.Test/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactoryTests.cs b/test/Core.Test/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactoryTests.cs new file mode 100644 index 0000000000..43dc416de1 --- /dev/null +++ b/test/Core.Test/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactoryTests.cs @@ -0,0 +1,92 @@ +using System.Reflection; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Licenses; +using Bit.Core.Billing.Licenses.Models; +using Bit.Core.Billing.Licenses.Services.Implementations; +using Bit.Core.Models.Business; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Billing.Licenses.Services.Implementations; + +public class OrganizationLicenseClaimsFactoryTests +{ + [Theory, BitAutoData] + public async Task GenerateClaims_CreatesClaimsForAllConstants(Organization organization) + { + // This test ensures that when a constant is added to OrganizationLicenseConstants, + // it is also added to the OrganizationLicenseClaimsFactory to generate claims. + // This is the second step in the license synchronization pipeline: + // Property → Constant → Claim → Extraction → Application + + // 1. Populate all nullable properties to ensure claims can be generated + // The factory only adds claims for properties that have values + organization.Name = "Test Organization"; + organization.BillingEmail = "billing@test.com"; + organization.BusinessName = "Test Business"; + organization.Plan = "Enterprise"; + organization.LicenseKey = "test-license-key"; + organization.Seats = 100; + organization.MaxCollections = 50; + organization.MaxStorageGb = 10; + organization.SmSeats = 25; + organization.SmServiceAccounts = 10; + organization.ExpirationDate = DateTime.UtcNow.AddYears(1); // Ensure org is not expired + + // Create a LicenseContext with a minimal SubscriptionInfo to trigger conditional claims + // ExpirationWithoutGracePeriod is only generated for active, non-trial, annual subscriptions + var licenseContext = new LicenseContext + { + InstallationId = Guid.NewGuid(), + SubscriptionInfo = new SubscriptionInfo + { + Subscription = new SubscriptionInfo.BillingSubscription(null!) + { + TrialEndDate = DateTime.UtcNow.AddDays(-30), // Trial ended in the past + PeriodStartDate = DateTime.UtcNow, + PeriodEndDate = DateTime.UtcNow.AddDays(365), // Annual subscription (>180 days) + Status = "active" + } + } + }; + + // 2. Generate claims + var factory = new OrganizationLicenseClaimsFactory(); + var claims = await factory.GenerateClaims(organization, licenseContext); + + // 3. Get all constants from OrganizationLicenseConstants + var allConstants = typeof(OrganizationLicenseConstants) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => f.IsLiteral && !f.IsInitOnly) + .Select(f => f.GetValue(null) as string) + .ToHashSet(); + + // 4. Get claim types from generated claims + var generatedClaimTypes = claims.Select(c => c.Type).ToHashSet(); + + // 5. Find constants that don't have corresponding claims + var constantsWithoutClaims = allConstants + .Except(generatedClaimTypes) + .OrderBy(c => c) + .ToList(); + + // 6. Build error message with guidance + var errorMessage = ""; + if (constantsWithoutClaims.Any()) + { + errorMessage = $"The following constants in OrganizationLicenseConstants are NOT generated as claims in OrganizationLicenseClaimsFactory:\n"; + errorMessage += string.Join("\n", constantsWithoutClaims.Select(c => $" - {c}")); + errorMessage += "\n\nPlease add the following claims to OrganizationLicenseClaimsFactory.GenerateClaims():\n"; + foreach (var constant in constantsWithoutClaims) + { + errorMessage += $" new(nameof(OrganizationLicenseConstants.{constant}), entity.{constant}.ToString()),\n"; + } + errorMessage += "\nNote: If the property is nullable, you may need to add it conditionally."; + } + + // 7. Assert - if this fails, the error message guides the developer to add claim generation + Assert.True( + !constantsWithoutClaims.Any(), + $"\n{errorMessage}"); + } +} diff --git a/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs b/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs index 8befbb000d..46c418e913 100644 --- a/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs +++ b/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs @@ -1,9 +1,14 @@ -using System.Security.Claims; +using System.Reflection; +using System.Security.Claims; +using System.Text.RegularExpressions; using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Licenses; using Bit.Core.Billing.Organizations.Commands; using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Services; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations; using Bit.Core.Services; using Bit.Core.Settings; @@ -99,6 +104,320 @@ public class UpdateOrganizationLicenseCommandTests } } + [Theory, BitAutoData] + public async Task UpdateLicenseAsync_WithClaimsPrincipal_ExtractsAllPropertiesFromClaims( + SelfHostedOrganizationDetails selfHostedOrg, + OrganizationLicense license, + SutProvider sutProvider) + { + var globalSettings = sutProvider.GetDependency(); + globalSettings.LicenseDirectory = LicenseDirectory; + globalSettings.SelfHosted = true; + + // Setup license for CanUse validation + license.Enabled = true; + license.Issued = DateTime.Now.AddDays(-1); + license.Expires = DateTime.Now.AddDays(1); + license.Version = OrganizationLicense.CurrentLicenseFileVersion; + license.InstallationId = globalSettings.Installation.Id; + license.LicenseType = LicenseType.Organization; + license.Token = "test-token"; // Indicates this is a claims-based license + sutProvider.GetDependency().VerifyLicense(license).Returns(true); + + // Create a ClaimsPrincipal with all organization license claims + var claims = new List + { + new(OrganizationLicenseConstants.LicenseType, ((int)LicenseType.Organization).ToString()), + new(OrganizationLicenseConstants.InstallationId, globalSettings.Installation.Id.ToString()), + new(OrganizationLicenseConstants.Name, "Test Organization"), + new(OrganizationLicenseConstants.BillingEmail, "billing@test.com"), + new(OrganizationLicenseConstants.BusinessName, "Test Business"), + new(OrganizationLicenseConstants.PlanType, ((int)PlanType.EnterpriseAnnually).ToString()), + new(OrganizationLicenseConstants.Seats, "100"), + new(OrganizationLicenseConstants.MaxCollections, "50"), + new(OrganizationLicenseConstants.UsePolicies, "true"), + new(OrganizationLicenseConstants.UseSso, "true"), + new(OrganizationLicenseConstants.UseKeyConnector, "true"), + new(OrganizationLicenseConstants.UseScim, "true"), + new(OrganizationLicenseConstants.UseGroups, "true"), + new(OrganizationLicenseConstants.UseDirectory, "true"), + new(OrganizationLicenseConstants.UseEvents, "true"), + new(OrganizationLicenseConstants.UseTotp, "true"), + new(OrganizationLicenseConstants.Use2fa, "true"), + new(OrganizationLicenseConstants.UseApi, "true"), + new(OrganizationLicenseConstants.UseResetPassword, "true"), + new(OrganizationLicenseConstants.Plan, "Enterprise"), + new(OrganizationLicenseConstants.SelfHost, "true"), + new(OrganizationLicenseConstants.UsersGetPremium, "true"), + new(OrganizationLicenseConstants.UseCustomPermissions, "true"), + new(OrganizationLicenseConstants.Enabled, "true"), + new(OrganizationLicenseConstants.Expires, DateTime.Now.AddDays(1).ToString("O")), + new(OrganizationLicenseConstants.LicenseKey, "test-license-key"), + new(OrganizationLicenseConstants.UsePasswordManager, "true"), + new(OrganizationLicenseConstants.UseSecretsManager, "true"), + new(OrganizationLicenseConstants.SmSeats, "25"), + new(OrganizationLicenseConstants.SmServiceAccounts, "10"), + new(OrganizationLicenseConstants.UseRiskInsights, "true"), + new(OrganizationLicenseConstants.UseOrganizationDomains, "true"), + new(OrganizationLicenseConstants.UseAdminSponsoredFamilies, "true"), + new(OrganizationLicenseConstants.UseAutomaticUserConfirmation, "true"), + new(OrganizationLicenseConstants.UseDisableSmAdsForUsers, "true"), + new(OrganizationLicenseConstants.UsePhishingBlocker, "true"), + new(OrganizationLicenseConstants.MaxStorageGb, "5"), + new(OrganizationLicenseConstants.Issued, DateTime.Now.AddDays(-1).ToString("O")), + new(OrganizationLicenseConstants.Refresh, DateTime.Now.AddMonths(1).ToString("O")), + new(OrganizationLicenseConstants.ExpirationWithoutGracePeriod, DateTime.Now.AddMonths(12).ToString("O")), + new(OrganizationLicenseConstants.Trial, "false"), + new(OrganizationLicenseConstants.LimitCollectionCreationDeletion, "true"), + new(OrganizationLicenseConstants.AllowAdminAccessToAllCollectionItems, "true") + }; + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims)); + + sutProvider.GetDependency() + .GetClaimsPrincipalFromLicense(license) + .Returns(claimsPrincipal); + + // Setup selfHostedOrg for CanUseLicense validation + selfHostedOrg.OccupiedSeatCount = 50; // Less than the 100 seats in the license + selfHostedOrg.CollectionCount = 10; // Less than the 50 max collections in the license + selfHostedOrg.GroupCount = 1; + selfHostedOrg.UseGroups = true; + selfHostedOrg.UsePolicies = true; + selfHostedOrg.UseSso = true; + selfHostedOrg.UseKeyConnector = true; + selfHostedOrg.UseScim = true; + selfHostedOrg.UseCustomPermissions = true; + selfHostedOrg.UseResetPassword = true; + + try + { + await sutProvider.Sut.UpdateLicenseAsync(selfHostedOrg, license, null); + + // Assertion: license file should be written to disk + var filePath = Path.Combine(LicenseDirectory, "organization", $"{selfHostedOrg.Id}.json"); + await using var fs = File.OpenRead(filePath); + var licenseFromFile = await JsonSerializer.DeserializeAsync(fs); + + AssertHelper.AssertPropertyEqual(license, licenseFromFile, "SignatureBytes"); + + // Assertion: organization should be updated with ALL properties extracted from claims + await sutProvider.GetDependency() + .Received(1) + .ReplaceAndUpdateCacheAsync(Arg.Is(org => + org.Name == "Test Organization" && + org.BillingEmail == "billing@test.com" && + org.BusinessName == "Test Business" && + org.PlanType == PlanType.EnterpriseAnnually && + org.Seats == 100 && + org.MaxCollections == 50 && + org.UsePolicies == true && + org.UseSso == true && + org.UseKeyConnector == true && + org.UseScim == true && + org.UseGroups == true && + org.UseDirectory == true && + org.UseEvents == true && + org.UseTotp == true && + org.Use2fa == true && + org.UseApi == true && + org.UseResetPassword == true && + org.Plan == "Enterprise" && + org.SelfHost == true && + org.UsersGetPremium == true && + org.UseCustomPermissions == true && + org.Enabled == true && + org.LicenseKey == "test-license-key" && + org.UsePasswordManager == true && + org.UseSecretsManager == true && + org.SmSeats == 25 && + org.SmServiceAccounts == 10 && + org.UseRiskInsights == true && + org.UseOrganizationDomains == true && + org.UseAdminSponsoredFamilies == true && + org.UseAutomaticUserConfirmation == true && + org.UseDisableSmAdsForUsers == true && + org.UsePhishingBlocker == true)); + } + finally + { + // Clean up temporary directory + if (Directory.Exists(OrganizationLicenseDirectory.Value)) + { + Directory.Delete(OrganizationLicenseDirectory.Value, true); + } + } + } + + [Theory, BitAutoData] + public async Task UpdateLicenseAsync_WrongInstallationIdInClaims_ThrowsBadRequestException( + SelfHostedOrganizationDetails selfHostedOrg, + OrganizationLicense license, + SutProvider sutProvider) + { + var globalSettings = sutProvider.GetDependency(); + globalSettings.LicenseDirectory = LicenseDirectory; + globalSettings.SelfHosted = true; + + // Setup license for CanUse validation + license.Enabled = true; + license.Issued = DateTime.Now.AddDays(-1); + license.Expires = DateTime.Now.AddDays(1); + license.Version = OrganizationLicense.CurrentLicenseFileVersion; + license.LicenseType = LicenseType.Organization; + license.Token = "test-token"; // Indicates this is a claims-based license + sutProvider.GetDependency().VerifyLicense(license).Returns(true); + + // Create a ClaimsPrincipal with WRONG installation ID + var wrongInstallationId = Guid.NewGuid(); // Different from globalSettings.Installation.Id + var claims = new List + { + new(OrganizationLicenseConstants.LicenseType, ((int)LicenseType.Organization).ToString()), + new(OrganizationLicenseConstants.InstallationId, wrongInstallationId.ToString()), + new(OrganizationLicenseConstants.Enabled, "true"), + new(OrganizationLicenseConstants.SelfHost, "true") + }; + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims)); + + sutProvider.GetDependency() + .GetClaimsPrincipalFromLicense(license) + .Returns(claimsPrincipal); + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateLicenseAsync(selfHostedOrg, license, null)); + + Assert.Contains("The installation ID does not match the current installation.", exception.Message); + + // Verify organization was NOT saved + await sutProvider.GetDependency() + .DidNotReceive() + .ReplaceAndUpdateCacheAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task UpdateLicenseAsync_ExpiredLicenseWithoutClaims_ThrowsBadRequestException( + SelfHostedOrganizationDetails selfHostedOrg, + OrganizationLicense license, + SutProvider sutProvider) + { + var globalSettings = sutProvider.GetDependency(); + globalSettings.LicenseDirectory = LicenseDirectory; + globalSettings.SelfHosted = true; + + // Setup legacy license (no Token, no claims) + license.Token = null; // Legacy license + license.Enabled = true; + license.Issued = DateTime.Now.AddDays(-2); + license.Expires = DateTime.Now.AddDays(-1); // Expired yesterday + license.Version = OrganizationLicense.CurrentLicenseFileVersion; + license.InstallationId = globalSettings.Installation.Id; + license.LicenseType = LicenseType.Organization; + license.SelfHost = true; + + sutProvider.GetDependency().VerifyLicense(license).Returns(true); + sutProvider.GetDependency() + .GetClaimsPrincipalFromLicense(license) + .Returns((ClaimsPrincipal)null); // No claims for legacy license + + // Passing values for SelfHostedOrganizationDetails.CanUseLicense + license.Seats = null; + license.MaxCollections = null; + license.UseGroups = true; + license.UsePolicies = true; + license.UseSso = true; + license.UseKeyConnector = true; + license.UseScim = true; + license.UseCustomPermissions = true; + license.UseResetPassword = true; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateLicenseAsync(selfHostedOrg, license, null)); + + Assert.Contains("The license has expired.", exception.Message); + + // Verify organization was NOT saved + await sutProvider.GetDependency() + .DidNotReceive() + .ReplaceAndUpdateCacheAsync(Arg.Any()); + } + + [Fact] + public async Task UpdateLicenseAsync_ExtractsAllClaimsBasedProperties_WhenClaimsPrincipalProvided() + { + // This test ensures that when new properties are added to OrganizationLicense, + // they are automatically extracted from JWT claims in UpdateOrganizationLicenseCommand. + // If a new constant is added to OrganizationLicenseConstants but not extracted, + // this test will fail with a clear message showing which properties are missing. + + // 1. Get all OrganizationLicenseConstants + var constantFields = typeof(OrganizationLicenseConstants) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.GetField) + .Where(f => f.IsLiteral && !f.IsInitOnly) + .Select(f => f.GetValue(null) as string) + .ToList(); + + // 2. Define properties that should be excluded (not claims-based or intentionally not extracted) + var excludedProperties = new HashSet + { + "Version", // Not in claims system (only in deprecated property-based licenses) + "Hash", // Signature-related, not extracted from claims + "Signature", // Signature-related, not extracted from claims + "SignatureBytes", // Computed from Signature, not a claim + "Token", // The JWT itself, not extracted from claims + "Id" // Cloud org ID from license, not used - self-hosted org has its own separate ID + }; + + // 3. Get properties that should be extracted from claims + var propertiesThatShouldBeExtracted = constantFields + .Where(c => !excludedProperties.Contains(c)) + .ToHashSet(); + + // 4. Read UpdateOrganizationLicenseCommand source code + var commandSourcePath = Path.Combine( + Directory.GetCurrentDirectory(), + "..", "..", "..", "..", "..", + "src", "Core", "Billing", "Organizations", "Commands", "UpdateOrganizationLicenseCommand.cs"); + var sourceCode = await File.ReadAllTextAsync(commandSourcePath); + + // 5. Find all GetValue calls that extract properties from claims + // Pattern matches: license.PropertyName = claimsPrincipal.GetValue(OrganizationLicenseConstants.PropertyName) + var extractedProperties = new HashSet(); + var getValuePattern = @"claimsPrincipal\.GetValue<[^>]+>\(OrganizationLicenseConstants\.(\w+)\)"; + var matches = Regex.Matches(sourceCode, getValuePattern); + + foreach (Match match in matches) + { + extractedProperties.Add(match.Groups[1].Value); + } + + // 6. Find missing extractions + var missingExtractions = propertiesThatShouldBeExtracted + .Except(extractedProperties) + .OrderBy(p => p) + .ToList(); + + // 7. Build error message with guidance if there are missing extractions + var errorMessage = ""; + if (missingExtractions.Any()) + { + errorMessage = $"The following constants in OrganizationLicenseConstants are NOT extracted from claims in UpdateOrganizationLicenseCommand:\n"; + errorMessage += string.Join("\n", missingExtractions.Select(p => $" - {p}")); + errorMessage += "\n\nPlease add the following lines to UpdateOrganizationLicenseCommand.cs in the 'if (claimsPrincipal != null)' block:\n"; + foreach (var prop in missingExtractions) + { + errorMessage += $" license.{prop} = claimsPrincipal.GetValue(OrganizationLicenseConstants.{prop});\n"; + } + } + + // 8. Assert - if this fails, the error message guides the developer to add the extraction + // Note: We don't check for "extra extractions" because that would be a compile error + // (can't reference OrganizationLicenseConstants.Foo if Foo doesn't exist) + Assert.True( + !missingExtractions.Any(), + $"\n{errorMessage}"); + } + // Wrapper to compare 2 objects that are different types private bool AssertPropertyEqual(OrganizationLicense expected, Organization actual, params string[] excludedPropertyStrings) { From 07b07216160e6af9a667f7884bc584a85d3c0dfc Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Tue, 13 Jan 2026 08:39:27 -0600 Subject: [PATCH 44/58] Removed old implementation and feature flag checks for bulk revoke. (#6827) --- .../OrganizationUsersController.cs | 5 -- .../v1/IRevokeOrganizationUserCommand.cs | 2 - .../v1/RevokeOrganizationUserCommand.cs | 64 ------------------- ...ganizationUserControllerBulkRevokeTests.cs | 9 --- 4 files changed, 80 deletions(-) diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 5cdd857f3f..90d02a46a1 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -665,11 +665,6 @@ public class OrganizationUsersController : BaseAdminConsoleController [Authorize] public async Task> BulkRevokeAsync(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model) { - if (!_featureService.IsEnabled(FeatureFlagKeys.BulkRevokeUsersV2)) - { - return await RestoreOrRevokeUsersAsync(orgId, model, _revokeOrganizationUserCommand.RevokeUsersAsync); - } - var currentUserId = _userService.GetProperUserId(User); if (currentUserId == null) { diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/IRevokeOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/IRevokeOrganizationUserCommand.cs index 7b5541c3ce..313c01af7c 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/IRevokeOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/IRevokeOrganizationUserCommand.cs @@ -7,6 +7,4 @@ public interface IRevokeOrganizationUserCommand { Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId); Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser); - Task>> RevokeUsersAsync(Guid organizationId, - IEnumerable organizationUserIds, Guid? revokingUserId); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/RevokeOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/RevokeOrganizationUserCommand.cs index 7aa67f0813..750ebf2518 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/RevokeOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeUser/v1/RevokeOrganizationUserCommand.cs @@ -68,68 +68,4 @@ public class RevokeOrganizationUserCommand( await organizationUserRepository.RevokeAsync(organizationUser.Id); organizationUser.Status = OrganizationUserStatusType.Revoked; } - - public async Task>> RevokeUsersAsync(Guid organizationId, - IEnumerable organizationUserIds, Guid? revokingUserId) - { - var orgUsers = await organizationUserRepository.GetManyAsync(organizationUserIds); - var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId) - .ToList(); - - if (!filteredUsers.Any()) - { - throw new BadRequestException("Users invalid."); - } - - if (!await hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, organizationUserIds)) - { - throw new BadRequestException("Organization must have at least one confirmed owner."); - } - - var deletingUserIsOwner = false; - if (revokingUserId.HasValue) - { - deletingUserIsOwner = await currentContext.OrganizationOwner(organizationId); - } - - var result = new List>(); - - foreach (var organizationUser in filteredUsers) - { - try - { - if (organizationUser.Status == OrganizationUserStatusType.Revoked) - { - throw new BadRequestException("Already revoked."); - } - - if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId) - { - throw new BadRequestException("You cannot revoke yourself."); - } - - if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue && - !deletingUserIsOwner) - { - throw new BadRequestException("Only owners can revoke other owners."); - } - - await organizationUserRepository.RevokeAsync(organizationUser.Id); - organizationUser.Status = OrganizationUserStatusType.Revoked; - await eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked); - if (organizationUser.UserId.HasValue) - { - await pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); - } - - result.Add(Tuple.Create(organizationUser, "")); - } - catch (BadRequestException e) - { - result.Add(Tuple.Create(organizationUser, e.Message)); - } - } - - return result; - } } diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerBulkRevokeTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerBulkRevokeTests.cs index 6645f29eae..3ea7d9ff5a 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerBulkRevokeTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerBulkRevokeTests.cs @@ -4,7 +4,6 @@ using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.IntegrationTest.Factories; using Bit.Api.IntegrationTest.Helpers; using Bit.Api.Models.Response; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; @@ -14,8 +13,6 @@ using Bit.Core.Billing.Enums; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Repositories; -using Bit.Core.Services; -using NSubstitute; using Xunit; namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; @@ -32,12 +29,6 @@ public class OrganizationUserControllerBulkRevokeTests : IClassFixture(featureService => - { - featureService - .IsEnabled(FeatureFlagKeys.BulkRevokeUsersV2) - .Returns(true); - }); _client = _factory.CreateClient(); _loginHelper = new LoginHelper(_factory, _client); } From a9f78487efcf245d304b477391bd57deae7bca80 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 Jan 2026 15:47:22 +0100 Subject: [PATCH 45/58] Add feature flag (#6820) --- src/Core/Constants.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 24e30fbcf0..47e7eb40bd 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -202,6 +202,7 @@ public static class FeatureFlagKeys public const string V2RegistrationTDEJIT = "pm-27279-v2-registration-tde-jit"; public const string DataRecoveryTool = "pm-28813-data-recovery-tool"; public const string EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration"; + public const string SdkKeyRotation = "pm-30144-sdk-key-rotation"; public const string EnableAccountEncryptionV2JitPasswordRegistration = "enable-account-encryption-v2-jit-password-registration"; /* Mobile Team */ From f144828a8775af4f32ae62ecd0179b8fac893a87 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 Jan 2026 18:10:01 +0100 Subject: [PATCH 46/58] [PM-22263] [PM-29849] Initial PoC of seeder API (#6424) We want to reduce the amount of business critical test data in the company. One way of doing that is to generate test data on demand prior to client side testing. Clients will request a scene to be set up with a JSON body set of options, specific to a given scene. Successful seed requests will be responded to with a mangleMap which maps magic strings present in the request to the mangled, non-colliding versions inserted into the database. This way, the server is solely responsible for understanding uniqueness requirements in the database. scenes also are able to return custom data, depending on the scene. For example, user creation would benefit from a return value of the userId for further test setup on the client side. Clients will indicate they are running tests by including a unique header, x-play-id which specifies a unique testing context. The server uses this PlayId as the seed for any mangling that occurs. This allows the client to decide it will reuse a given PlayId if the test context builds on top of previously executed tests. When a given context is no longer needed, the API user will delete all test data associated with the PlayId by calling a delete endpoint. --------- Co-authored-by: Matt Gibson --- .vscode/launch.json | 84 + .vscode/tasks.json | 69 + bitwarden-server.sln | 13 + bitwarden_license/src/Scim/Startup.cs | 1 + bitwarden_license/src/Sso/Startup.cs | 1 + dev/setup_secrets.ps1 | 23 +- src/Admin/Startup.cs | 1 + .../Request/EmergencyAccessRequestModels.cs | 2 +- src/Api/Startup.cs | 1 + src/Billing/Startup.cs | 1 + src/Core/Auth/Entities/EmergencyAccess.cs | 2 +- .../EmergencyAccess/EmergencyAccessService.cs | 2 +- src/Core/Entities/PlayItem.cs | 60 + src/Core/Repositories/IPlayItemRepository.cs | 11 + src/Core/Services/Play/IPlayIdService.cs | 23 + src/Core/Services/Play/IPlayItemService.cs | 27 + .../Implementations/NeverPlayIdServices.cs | 16 + .../Play/Implementations/PlayIdService.cs | 13 + .../Implementations/PlayIdSingletonService.cs | 48 + .../Play/Implementations/PlayItemService.cs | 26 + src/Core/Services/Play/README.md | 27 + src/Core/Settings/GlobalSettings.cs | 1 + src/Events/Startup.cs | 1 + src/EventsProcessor/Startup.cs | 1 + src/Identity/Startup.cs | 1 + .../Repositories/OrganizationRepository.cs | 2 +- .../DapperServiceCollectionExtensions.cs | 1 + .../Repositories/PlayItemRepository.cs | 45 + .../Repositories/OrganizationRepository.cs | 2 +- .../PlayItemEntityTypeConfiguration.cs | 46 + ...ityFrameworkServiceCollectionExtensions.cs | 1 + .../Models/PlayItem.cs | 19 + .../Repositories/DatabaseContext.cs | 1 + .../Repositories/PlayItemRepository.cs | 42 + .../Play/PlayServiceCollectionExtensions.cs | 30 + ...anizationTrackingOrganizationRepository.cs | 32 + .../DapperTestUserTrackingUserRepository.cs | 33 + ...anizationTrackingOrganizationRepository.cs | 33 + .../EFTestUserTrackingUserRepository.cs | 31 + src/SharedWeb/Utilities/PlayIdMiddleware.cs | 41 + .../Utilities/ServiceCollectionExtensions.cs | 39 + .../dbo/Stored Procedures/PlayItem_Create.sql | 27 + .../PlayItem_DeleteByPlayId.sql | 12 + .../PlayItem_ReadByPlayId.sql | 17 + src/Sql/dbo/Tables/PlayItem.sql | 23 + test/Common/AutoFixture/SutProvider.cs | 14 + test/Core.Test/Services/PlayIdServiceTests.cs | 211 + .../Services/PlayItemServiceTests.cs | 143 + .../DatabaseDataAttribute.cs | 4 +- .../Factories/WebApplicationFactoryBase.cs | 2 +- .../HttpClientExtensions.cs | 40 + .../QueryControllerTest.cs | 75 + .../SeedControllerTest.cs | 222 ++ .../SeederApi.IntegrationTest.csproj | 29 + .../SeederApiApplicationFactory.cs | 18 + test/SharedWeb.Test/PlayIdMiddlewareTests.cs | 102 + .../2026-01-08_00_CreatePlayItem.sql | 90 + .../20260108193951_CreatePlayItem.Designer.cs | 3502 ++++++++++++++++ .../20260108193951_CreatePlayItem.cs | 65 + .../DatabaseContextModelSnapshot.cs | 231 +- .../20260108193909_CreatePlayItem.Designer.cs | 3508 +++++++++++++++++ .../20260108193909_CreatePlayItem.cs | 63 + .../DatabaseContextModelSnapshot.cs | 231 +- util/RustSdk/RustSdk.csproj | 33 +- util/RustSdk/rust-toolchain.toml | 2 + util/Seeder/Factories/OrganizationSeeder.cs | 2 +- util/Seeder/Factories/UserSeeder.cs | 96 +- util/Seeder/IQuery.cs | 60 + util/Seeder/IScene.cs | 96 + util/Seeder/MangleId.cs | 19 + .../Queries/EmergencyAccessInviteQuery.cs | 35 + .../Recipes/OrganizationWithUsersRecipe.cs | 8 +- util/Seeder/SceneResult.cs | 28 + util/Seeder/Scenes/SingleUserScene.cs | 38 + util/Seeder/Seeder.csproj | 4 - .../Commands/DestroyBatchScenesCommand.cs | 36 + .../SeederApi/Commands/DestroySceneCommand.cs | 57 + .../Interfaces/IDestroyBatchScenesCommand.cs | 14 + .../Interfaces/IDestroySceneCommand.cs | 15 + util/SeederApi/Controllers/InfoController.cs | 20 + util/SeederApi/Controllers/QueryController.cs | 32 + util/SeederApi/Controllers/SeedController.cs | 100 + util/SeederApi/Execution/IQueryExecutor.cs | 22 + util/SeederApi/Execution/ISceneExecutor.cs | 22 + util/SeederApi/Execution/JsonConfiguration.cs | 19 + util/SeederApi/Execution/QueryExecutor.cs | 77 + util/SeederApi/Execution/SceneExecutor.cs | 78 + .../Extensions/ServiceCollectionExtensions.cs | 76 + .../Models/Request/QueryRequestModel.cs | 11 + .../Models/Request/SeedRequestModel.cs | 11 + .../Models/Response/SeedResponseModel.cs | 18 + util/SeederApi/Program.cs | 20 + util/SeederApi/Properties/launchSettings.json | 37 + util/SeederApi/Queries/GetAllPlayIdsQuery.cs | 15 + .../Queries/Interfaces/IGetAllPlayIdsQuery.cs | 13 + util/SeederApi/README.md | 185 + util/SeederApi/SeederApi.csproj | 16 + util/SeederApi/Services/QueryExceptions.cs | 10 + util/SeederApi/Services/SceneExceptions.cs | 10 + util/SeederApi/Startup.cs | 80 + util/SeederApi/appsettings.Development.json | 8 + util/SeederApi/appsettings.json | 11 + .../20260108193841_CreatePlayItem.Designer.cs | 3491 ++++++++++++++++ .../20260108193841_CreatePlayItem.cs | 63 + .../DatabaseContextModelSnapshot.cs | 229 +- 105 files changed, 14377 insertions(+), 322 deletions(-) mode change 100644 => 100755 dev/setup_secrets.ps1 create mode 100644 src/Core/Entities/PlayItem.cs create mode 100644 src/Core/Repositories/IPlayItemRepository.cs create mode 100644 src/Core/Services/Play/IPlayIdService.cs create mode 100644 src/Core/Services/Play/IPlayItemService.cs create mode 100644 src/Core/Services/Play/Implementations/NeverPlayIdServices.cs create mode 100644 src/Core/Services/Play/Implementations/PlayIdService.cs create mode 100644 src/Core/Services/Play/Implementations/PlayIdSingletonService.cs create mode 100644 src/Core/Services/Play/Implementations/PlayItemService.cs create mode 100644 src/Core/Services/Play/README.md create mode 100644 src/Infrastructure.Dapper/Repositories/PlayItemRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Configurations/PlayItemEntityTypeConfiguration.cs create mode 100644 src/Infrastructure.EntityFramework/Models/PlayItem.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/PlayItemRepository.cs create mode 100644 src/SharedWeb/Play/PlayServiceCollectionExtensions.cs create mode 100644 src/SharedWeb/Play/Repositories/DapperTestOrganizationTrackingOrganizationRepository.cs create mode 100644 src/SharedWeb/Play/Repositories/DapperTestUserTrackingUserRepository.cs create mode 100644 src/SharedWeb/Play/Repositories/EFTestOrganizationTrackingOrganizationRepository.cs create mode 100644 src/SharedWeb/Play/Repositories/EFTestUserTrackingUserRepository.cs create mode 100644 src/SharedWeb/Utilities/PlayIdMiddleware.cs create mode 100644 src/Sql/dbo/Stored Procedures/PlayItem_Create.sql create mode 100644 src/Sql/dbo/Stored Procedures/PlayItem_DeleteByPlayId.sql create mode 100644 src/Sql/dbo/Stored Procedures/PlayItem_ReadByPlayId.sql create mode 100644 src/Sql/dbo/Tables/PlayItem.sql create mode 100644 test/Core.Test/Services/PlayIdServiceTests.cs create mode 100644 test/Core.Test/Services/PlayItemServiceTests.cs create mode 100644 test/SeederApi.IntegrationTest/HttpClientExtensions.cs create mode 100644 test/SeederApi.IntegrationTest/QueryControllerTest.cs create mode 100644 test/SeederApi.IntegrationTest/SeedControllerTest.cs create mode 100644 test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csproj create mode 100644 test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs create mode 100644 test/SharedWeb.Test/PlayIdMiddlewareTests.cs create mode 100644 util/Migrator/DbScripts/2026-01-08_00_CreatePlayItem.sql create mode 100644 util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.cs create mode 100644 util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.cs create mode 100644 util/RustSdk/rust-toolchain.toml create mode 100644 util/Seeder/IQuery.cs create mode 100644 util/Seeder/IScene.cs create mode 100644 util/Seeder/MangleId.cs create mode 100644 util/Seeder/Queries/EmergencyAccessInviteQuery.cs create mode 100644 util/Seeder/SceneResult.cs create mode 100644 util/Seeder/Scenes/SingleUserScene.cs create mode 100644 util/SeederApi/Commands/DestroyBatchScenesCommand.cs create mode 100644 util/SeederApi/Commands/DestroySceneCommand.cs create mode 100644 util/SeederApi/Commands/Interfaces/IDestroyBatchScenesCommand.cs create mode 100644 util/SeederApi/Commands/Interfaces/IDestroySceneCommand.cs create mode 100644 util/SeederApi/Controllers/InfoController.cs create mode 100644 util/SeederApi/Controllers/QueryController.cs create mode 100644 util/SeederApi/Controllers/SeedController.cs create mode 100644 util/SeederApi/Execution/IQueryExecutor.cs create mode 100644 util/SeederApi/Execution/ISceneExecutor.cs create mode 100644 util/SeederApi/Execution/JsonConfiguration.cs create mode 100644 util/SeederApi/Execution/QueryExecutor.cs create mode 100644 util/SeederApi/Execution/SceneExecutor.cs create mode 100644 util/SeederApi/Extensions/ServiceCollectionExtensions.cs create mode 100644 util/SeederApi/Models/Request/QueryRequestModel.cs create mode 100644 util/SeederApi/Models/Request/SeedRequestModel.cs create mode 100644 util/SeederApi/Models/Response/SeedResponseModel.cs create mode 100644 util/SeederApi/Program.cs create mode 100644 util/SeederApi/Properties/launchSettings.json create mode 100644 util/SeederApi/Queries/GetAllPlayIdsQuery.cs create mode 100644 util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs create mode 100644 util/SeederApi/README.md create mode 100644 util/SeederApi/SeederApi.csproj create mode 100644 util/SeederApi/Services/QueryExceptions.cs create mode 100644 util/SeederApi/Services/SceneExceptions.cs create mode 100644 util/SeederApi/Startup.cs create mode 100644 util/SeederApi/appsettings.Development.json create mode 100644 util/SeederApi/appsettings.json create mode 100644 util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index c407ba5604..74115dcc86 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -69,6 +69,28 @@ "preLaunchTask": "buildFullServer", "stopAll": true }, + { + "name": "Full Server with Seeder API", + "configurations": [ + "run-Admin", + "run-API", + "run-Events", + "run-EventsProcessor", + "run-Identity", + "run-Sso", + "run-Icons", + "run-Billing", + "run-Notifications", + "run-SeederAPI" + ], + "presentation": { + "hidden": false, + "group": "AA_compounds", + "order": 6 + }, + "preLaunchTask": "buildFullServerWithSeederApi", + "stopAll": true + }, { "name": "Self Host: Bit", "configurations": [ @@ -204,6 +226,17 @@ }, "preLaunchTask": "buildSso", }, + { + "name": "Seeder API", + "configurations": [ + "run-SeederAPI" + ], + "presentation": { + "hidden": false, + "group": "cloud", + }, + "preLaunchTask": "buildSeederAPI", + }, { "name": "Admin Self Host", "configurations": [ @@ -270,6 +303,17 @@ }, "preLaunchTask": "buildSso", }, + { + "name": "Seeder API Self Host", + "configurations": [ + "run-SeederAPI-SelfHost" + ], + "presentation": { + "hidden": false, + "group": "self-host", + }, + "preLaunchTask": "buildSeederAPI", + } ], "configurations": [ // Configurations represent run-only scenarios so that they can be used in multiple compounds @@ -311,6 +355,25 @@ "/Views": "${workspaceFolder}/Views" } }, + { + "name": "run-SeederAPI", + "presentation": { + "hidden": true, + }, + "requireExactSource": true, + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/util/SeederApi/bin/Debug/net8.0/SeederApi.dll", + "args": [], + "cwd": "${workspaceFolder}/util/SeederApi", + "stopAtEntry": false, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, { "name": "run-Billing", "presentation": { @@ -488,6 +551,27 @@ "/Views": "${workspaceFolder}/Views" } }, + { + "name": "run-SeederAPI-SelfHost", + "presentation": { + "hidden": true, + }, + "requireExactSource": true, + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/util/SeederApi/bin/Debug/net8.0/SeederApi.dll", + "args": [], + "cwd": "${workspaceFolder}/util/SeederApi", + "stopAtEntry": false, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5048", + "developSelfHosted": "true", + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, { "name": "run-Admin-SelfHost", "presentation": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 567f9b6e58..07a55fdeb3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -43,6 +43,21 @@ "label": "buildFullServer", "hide": true, "dependsOrder": "sequence", + "dependsOn": [ + "buildAdmin", + "buildAPI", + "buildEventsProcessor", + "buildIdentity", + "buildSso", + "buildIcons", + "buildBilling", + "buildNotifications" + ], + }, + { + "label": "buildFullServerWithSeederApi", + "hide": true, + "dependsOrder": "sequence", "dependsOn": [ "buildAdmin", "buildAPI", @@ -52,6 +67,7 @@ "buildIcons", "buildBilling", "buildNotifications", + "buildSeederAPI" ], }, { @@ -89,6 +105,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -102,6 +121,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -115,6 +137,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -128,6 +153,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -141,6 +169,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -154,6 +185,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -167,6 +201,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile" }, { @@ -180,6 +217,29 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "buildSeederAPI", + "hide": true, + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/util/SeederApi/SeederApi.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile", "group": { "kind": "build", @@ -197,6 +257,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile", "group": { "kind": "build", @@ -214,6 +277,9 @@ "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], + "options": { + "cwd": "${workspaceFolder}" + }, "problemMatcher": "$msCompile", "group": { "kind": "build", @@ -224,6 +290,9 @@ "label": "test", "type": "shell", "command": "dotnet test", + "options": { + "cwd": "${workspaceFolder}" + }, "group": { "kind": "test", "isDefault": true diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 055811478d..ae9571a4a5 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -137,6 +137,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RustSdk", "util\RustSdk\Rus EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedWeb.Test", "test\SharedWeb.Test\SharedWeb.Test.csproj", "{AD59537D-5259-4B7A-948F-0CF58E80B359}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi\SeederApi.csproj", "{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi.IntegrationTest", "test\SeederApi.IntegrationTest\SeederApi.IntegrationTest.csproj", "{A2E067EF-609C-4D13-895A-E054C61D48BB}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO.Test", "bitwarden_license\test\SSO.Test\SSO.Test.csproj", "{7D98784C-C253-43FB-9873-25B65C6250D6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitwarden_license\test\Sso.IntegrationTest\Sso.IntegrationTest.csproj", "{FFB09376-595B-6F93-36F0-70CAE90AFECB}" @@ -353,6 +356,14 @@ Global {AD59537D-5259-4B7A-948F-0CF58E80B359}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD59537D-5259-4B7A-948F-0CF58E80B359}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD59537D-5259-4B7A-948F-0CF58E80B359}.Release|Any CPU.Build.0 = Release|Any CPU + {9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}.Release|Any CPU.Build.0 = Release|Any CPU + {A2E067EF-609C-4D13-895A-E054C61D48BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2E067EF-609C-4D13-895A-E054C61D48BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2E067EF-609C-4D13-895A-E054C61D48BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2E067EF-609C-4D13-895A-E054C61D48BB}.Release|Any CPU.Build.0 = Release|Any CPU {7D98784C-C253-43FB-9873-25B65C6250D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D98784C-C253-43FB-9873-25B65C6250D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D98784C-C253-43FB-9873-25B65C6250D6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -417,6 +428,8 @@ Global {17A89266-260A-4A03-81AE-C0468C6EE06E} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} {D1513D90-E4F5-44A9-9121-5E46E3E4A3F7} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} {AD59537D-5259-4B7A-948F-0CF58E80B359} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} + {9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} + {A2E067EF-609C-4D13-895A-E054C61D48BB} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} {7D98784C-C253-43FB-9873-25B65C6250D6} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B} {FFB09376-595B-6F93-36F0-70CAE90AFECB} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B} EndGlobalSection diff --git a/bitwarden_license/src/Scim/Startup.cs b/bitwarden_license/src/Scim/Startup.cs index 2a84faa8dd..a912562f72 100644 --- a/bitwarden_license/src/Scim/Startup.cs +++ b/bitwarden_license/src/Scim/Startup.cs @@ -44,6 +44,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/bitwarden_license/src/Sso/Startup.cs b/bitwarden_license/src/Sso/Startup.cs index 2f83f3dad0..a2f363d533 100644 --- a/bitwarden_license/src/Sso/Startup.cs +++ b/bitwarden_license/src/Sso/Startup.cs @@ -41,6 +41,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/dev/setup_secrets.ps1 b/dev/setup_secrets.ps1 old mode 100644 new mode 100755 index 96dff04632..5013ca8bac --- a/dev/setup_secrets.ps1 +++ b/dev/setup_secrets.ps1 @@ -2,7 +2,7 @@ # Helper script for applying the same user secrets to each project param ( [switch]$clear, - [Parameter(ValueFromRemainingArguments = $true, Position=1)] + [Parameter(ValueFromRemainingArguments = $true, Position = 1)] $cmdArgs ) @@ -16,17 +16,18 @@ if ($clear -eq $true) { } $projects = @{ - Admin = "../src/Admin" - Api = "../src/Api" - Billing = "../src/Billing" - Events = "../src/Events" - EventsProcessor = "../src/EventsProcessor" - Icons = "../src/Icons" - Identity = "../src/Identity" - Notifications = "../src/Notifications" - Sso = "../bitwarden_license/src/Sso" - Scim = "../bitwarden_license/src/Scim" + Admin = "../src/Admin" + Api = "../src/Api" + Billing = "../src/Billing" + Events = "../src/Events" + EventsProcessor = "../src/EventsProcessor" + Icons = "../src/Icons" + Identity = "../src/Identity" + Notifications = "../src/Notifications" + Sso = "../bitwarden_license/src/Sso" + Scim = "../bitwarden_license/src/Scim" IntegrationTests = "../test/Infrastructure.IntegrationTest" + SeederApi = "../util/SeederApi" } foreach ($key in $projects.keys) { diff --git a/src/Admin/Startup.cs b/src/Admin/Startup.cs index 87d68a7ac6..6c0a644ee6 100644 --- a/src/Admin/Startup.cs +++ b/src/Admin/Startup.cs @@ -65,6 +65,7 @@ public class Startup default: break; } + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/src/Api/Auth/Models/Request/EmergencyAccessRequestModels.cs b/src/Api/Auth/Models/Request/EmergencyAccessRequestModels.cs index 33a7e52791..75e96ebc66 100644 --- a/src/Api/Auth/Models/Request/EmergencyAccessRequestModels.cs +++ b/src/Api/Auth/Models/Request/EmergencyAccessRequestModels.cs @@ -36,7 +36,7 @@ public class EmergencyAccessUpdateRequestModel existingEmergencyAccess.KeyEncrypted = KeyEncrypted; } existingEmergencyAccess.Type = Type; - existingEmergencyAccess.WaitTimeDays = WaitTimeDays; + existingEmergencyAccess.WaitTimeDays = (short)WaitTimeDays; return existingEmergencyAccess; } } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 2f16470cd4..b201cef0f3 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -85,6 +85,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/src/Billing/Startup.cs b/src/Billing/Startup.cs index 30f4f5f562..f5f98bfd53 100644 --- a/src/Billing/Startup.cs +++ b/src/Billing/Startup.cs @@ -48,6 +48,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // PayPal IPN Client services.AddHttpClient(); diff --git a/src/Core/Auth/Entities/EmergencyAccess.cs b/src/Core/Auth/Entities/EmergencyAccess.cs index d855126468..36aaf46a8c 100644 --- a/src/Core/Auth/Entities/EmergencyAccess.cs +++ b/src/Core/Auth/Entities/EmergencyAccess.cs @@ -18,7 +18,7 @@ public class EmergencyAccess : ITableObject public string KeyEncrypted { get; set; } public EmergencyAccessType Type { get; set; } public EmergencyAccessStatusType Status { get; set; } - public int WaitTimeDays { get; set; } + public short WaitTimeDays { get; set; } public DateTime? RecoveryInitiatedDate { get; set; } public DateTime? LastNotificationDate { get; set; } public DateTime CreationDate { get; set; } = DateTime.UtcNow; diff --git a/src/Core/Auth/Services/EmergencyAccess/EmergencyAccessService.cs b/src/Core/Auth/Services/EmergencyAccess/EmergencyAccessService.cs index 4331179554..0072f85e61 100644 --- a/src/Core/Auth/Services/EmergencyAccess/EmergencyAccessService.cs +++ b/src/Core/Auth/Services/EmergencyAccess/EmergencyAccessService.cs @@ -79,7 +79,7 @@ public class EmergencyAccessService : IEmergencyAccessService Email = emergencyContactEmail.ToLowerInvariant(), Status = EmergencyAccessStatusType.Invited, Type = accessType, - WaitTimeDays = waitTime, + WaitTimeDays = (short)waitTime, CreationDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow, }; diff --git a/src/Core/Entities/PlayItem.cs b/src/Core/Entities/PlayItem.cs new file mode 100644 index 0000000000..cf2f5c946b --- /dev/null +++ b/src/Core/Entities/PlayItem.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.Entities; + +/// +/// PlayItem is a join table tracking entities created during automated testing. +/// A `PlayId` is supplied by the clients in the `x-play-id` header to inform the server +/// that any data created should be associated with the play, and therefore cleaned up with it. +/// +public class PlayItem : ITableObject +{ + public Guid Id { get; set; } + [MaxLength(256)] + public required string PlayId { get; init; } + public Guid? UserId { get; init; } + public Guid? OrganizationId { get; init; } + public DateTime CreationDate { get; init; } + + /// + /// Generates and sets a new COMB GUID for the Id property. + /// + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } + + /// + /// Creates a new PlayItem record associated with a User. + /// + /// The user entity created during the play. + /// The play identifier from the x-play-id header. + /// A new PlayItem instance tracking the user. + public static PlayItem Create(User user, string playId) + { + return new PlayItem + { + PlayId = playId, + UserId = user.Id, + CreationDate = DateTime.UtcNow + }; + } + + /// + /// Creates a new PlayItem record associated with an Organization. + /// + /// The organization entity created during the play. + /// The play identifier from the x-play-id header. + /// A new PlayItem instance tracking the organization. + public static PlayItem Create(Organization organization, string playId) + { + return new PlayItem + { + PlayId = playId, + OrganizationId = organization.Id, + CreationDate = DateTime.UtcNow + }; + } +} diff --git a/src/Core/Repositories/IPlayItemRepository.cs b/src/Core/Repositories/IPlayItemRepository.cs new file mode 100644 index 0000000000..fb00be2bc1 --- /dev/null +++ b/src/Core/Repositories/IPlayItemRepository.cs @@ -0,0 +1,11 @@ +using Bit.Core.Entities; + +#nullable enable + +namespace Bit.Core.Repositories; + +public interface IPlayItemRepository : IRepository +{ + Task> GetByPlayIdAsync(string playId); + Task DeleteByPlayIdAsync(string playId); +} diff --git a/src/Core/Services/Play/IPlayIdService.cs b/src/Core/Services/Play/IPlayIdService.cs new file mode 100644 index 0000000000..542de9725f --- /dev/null +++ b/src/Core/Services/Play/IPlayIdService.cs @@ -0,0 +1,23 @@ +namespace Bit.Core.Services; + +/// +/// Service for managing Play identifiers in automated testing infrastructure. +/// A "Play" is a test session that groups entities created during testing to enable cleanup. +/// The PlayId flows from client request (x-play-id header) through PlayIdMiddleware to this service, +/// which repositories query to create PlayItem tracking records via IPlayItemService. The SeederAPI uses these records +/// to bulk delete all entities associated with a PlayId. Only active in Development environments. +/// +public interface IPlayIdService +{ + /// + /// Gets or sets the current Play identifier from the x-play-id request header. + /// + string? PlayId { get; set; } + + /// + /// Checks whether the current request is part of an active Play session. + /// + /// The Play identifier if active, otherwise empty string. + /// True if in a Play session (has PlayId and in Development environment), otherwise false. + bool InPlay(out string playId); +} diff --git a/src/Core/Services/Play/IPlayItemService.cs b/src/Core/Services/Play/IPlayItemService.cs new file mode 100644 index 0000000000..5033aaf626 --- /dev/null +++ b/src/Core/Services/Play/IPlayItemService.cs @@ -0,0 +1,27 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; + +namespace Bit.Core.Services; + +/// +/// Service used to track added users and organizations during a Play session. +/// +public interface IPlayItemService +{ + /// + /// Records a PlayItem entry for the given User created during a Play session. + /// + /// Does nothing if no Play Id is set for this http scope. + /// + /// + /// + Task Record(User user); + /// + /// Records a PlayItem entry for the given Organization created during a Play session. + /// + /// Does nothing if no Play Id is set for this http scope. + /// + /// + /// + Task Record(Organization organization); +} diff --git a/src/Core/Services/Play/Implementations/NeverPlayIdServices.cs b/src/Core/Services/Play/Implementations/NeverPlayIdServices.cs new file mode 100644 index 0000000000..baab44eedb --- /dev/null +++ b/src/Core/Services/Play/Implementations/NeverPlayIdServices.cs @@ -0,0 +1,16 @@ +namespace Bit.Core.Services; + +public class NeverPlayIdServices : IPlayIdService +{ + public string? PlayId + { + get => null; + set { } + } + + public bool InPlay(out string playId) + { + playId = string.Empty; + return false; + } +} diff --git a/src/Core/Services/Play/Implementations/PlayIdService.cs b/src/Core/Services/Play/Implementations/PlayIdService.cs new file mode 100644 index 0000000000..0c64f1dd14 --- /dev/null +++ b/src/Core/Services/Play/Implementations/PlayIdService.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.Hosting; + +namespace Bit.Core.Services; + +public class PlayIdService(IHostEnvironment hostEnvironment) : IPlayIdService +{ + public string? PlayId { get; set; } + public bool InPlay(out string playId) + { + playId = PlayId ?? string.Empty; + return !string.IsNullOrEmpty(PlayId) && hostEnvironment.IsDevelopment(); + } +} diff --git a/src/Core/Services/Play/Implementations/PlayIdSingletonService.cs b/src/Core/Services/Play/Implementations/PlayIdSingletonService.cs new file mode 100644 index 0000000000..30bb30aad2 --- /dev/null +++ b/src/Core/Services/Play/Implementations/PlayIdSingletonService.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Bit.Core.Services; + +/// +/// Singleton wrapper service that bridges singleton-scoped service boundaries for PlayId tracking. +/// This allows singleton services to access the scoped PlayIdService via HttpContext.RequestServices. +/// +/// Uses IHttpContextAccessor to retrieve the current request's scoped PlayIdService instance, enabling +/// singleton services to participate in Play session tracking without violating DI lifetime rules. +/// Falls back to NeverPlayIdServices when no HttpContext is available (e.g., background jobs). +/// +public class PlayIdSingletonService(IHttpContextAccessor httpContextAccessor, IHostEnvironment hostEnvironment) : IPlayIdService +{ + private IPlayIdService Current + { + get + { + var httpContext = httpContextAccessor.HttpContext; + if (httpContext == null) + { + return new NeverPlayIdServices(); + } + return httpContext.RequestServices.GetRequiredService(); + } + } + + public string? PlayId + { + get => Current.PlayId; + set => Current.PlayId = value; + } + + public bool InPlay(out string playId) + { + if (hostEnvironment.IsDevelopment()) + { + return Current.InPlay(out playId); + } + else + { + playId = string.Empty; + return false; + } + } +} diff --git a/src/Core/Services/Play/Implementations/PlayItemService.cs b/src/Core/Services/Play/Implementations/PlayItemService.cs new file mode 100644 index 0000000000..981445f2ea --- /dev/null +++ b/src/Core/Services/Play/Implementations/PlayItemService.cs @@ -0,0 +1,26 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Services; + +public class PlayItemService(IPlayIdService playIdService, IPlayItemRepository playItemRepository, ILogger logger) : IPlayItemService +{ + public async Task Record(User user) + { + if (playIdService.InPlay(out var playId)) + { + logger.LogInformation("Associating user {UserId} with Play ID {PlayId}", user.Id, playId); + await playItemRepository.CreateAsync(PlayItem.Create(user, playId)); + } + } + public async Task Record(Organization organization) + { + if (playIdService.InPlay(out var playId)) + { + logger.LogInformation("Associating organization {OrganizationId} with Play ID {PlayId}", organization.Id, playId); + await playItemRepository.CreateAsync(PlayItem.Create(organization, playId)); + } + } +} diff --git a/src/Core/Services/Play/README.md b/src/Core/Services/Play/README.md new file mode 100644 index 0000000000..b04b6a13c7 --- /dev/null +++ b/src/Core/Services/Play/README.md @@ -0,0 +1,27 @@ +# Play Services + +## Overview + +The Play services provide automated testing infrastructure for tracking and cleaning up test data in development +environments. A "Play" is a test session that groups entities (users, organizations, etc.) created during testing to +enable bulk cleanup via the SeederAPI. + +## How It Works + +1. Test client sends `x-play-id` header with a unique Play identifier +2. `PlayIdMiddleware` extracts the header and sets it on `IPlayIdService` +3. Repositories check `IPlayIdService.InPlay()` when creating entities +4. `IPlayItemService` records PlayItem entries for tracked entities +5. SeederAPI uses PlayItem records to bulk delete all entities associated with a PlayId + +Play services are **only active in Development environments**. + +## Classes + +- **`IPlayIdService`** - Interface for managing Play identifiers in the current request scope +- **`IPlayItemService`** - Interface for tracking entities created during a Play session +- **`PlayIdService`** - Default scoped implementation for tracking Play sessions per HTTP request +- **`NeverPlayIdServices`** - No-op implementation used as fallback when no HttpContext is available +- **`PlayIdSingletonService`** - Singleton wrapper that allows singleton services to access scoped PlayIdService via + HttpContext +- **`PlayItemService`** - Implementation that records PlayItem entries for entities created during Play sessions diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 60a1fda19f..1f4fa6104b 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -44,6 +44,7 @@ public class GlobalSettings : IGlobalSettings public virtual bool EnableCloudCommunication { get; set; } = false; public virtual int OrganizationInviteExpirationHours { get; set; } = 120; // 5 days public virtual string EventGridKey { get; set; } + public virtual bool TestPlayIdTrackingEnabled { get; set; } = false; public virtual IInstallationSettings Installation { get; set; } = new InstallationSettings(); public virtual IBaseServiceUriSettings BaseServiceUri { get; set; } public virtual string DatabaseProvider { get; set; } diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index 75301cf08c..d97d65c2ed 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -36,6 +36,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/src/EventsProcessor/Startup.cs b/src/EventsProcessor/Startup.cs index 888dda43a1..239393a693 100644 --- a/src/EventsProcessor/Startup.cs +++ b/src/EventsProcessor/Startup.cs @@ -30,6 +30,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Add event integration services services.AddDistributedCache(globalSettings); diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index 5dc443a73c..9d5536fd10 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -49,6 +49,7 @@ public class Startup // Repositories services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); // Context services.AddScoped(); diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs index 96ddc8c7da..434edb7676 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs @@ -17,7 +17,7 @@ namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationRepository : Repository, IOrganizationRepository { - private readonly ILogger _logger; + protected readonly ILogger _logger; public OrganizationRepository( GlobalSettings globalSettings, diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index e3ee82270f..dcb0dc1306 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -51,6 +51,7 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure.Dapper/Repositories/PlayItemRepository.cs b/src/Infrastructure.Dapper/Repositories/PlayItemRepository.cs new file mode 100644 index 0000000000..1fa8e88ce8 --- /dev/null +++ b/src/Infrastructure.Dapper/Repositories/PlayItemRepository.cs @@ -0,0 +1,45 @@ +using System.Data; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Dapper; +using Microsoft.Data.SqlClient; + +#nullable enable + +namespace Bit.Infrastructure.Dapper.Repositories; + +public class PlayItemRepository : Repository, IPlayItemRepository +{ + public PlayItemRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { } + + public PlayItemRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { } + + public async Task> GetByPlayIdAsync(string playId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[PlayItem_ReadByPlayId]", + new { PlayId = playId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task DeleteByPlayIdAsync(string playId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.ExecuteAsync( + "[dbo].[PlayItem_DeleteByPlayId]", + new { PlayId = playId }, + commandType: CommandType.StoredProcedure); + } + } +} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs index 89f0bb5806..88410facf5 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs @@ -20,7 +20,7 @@ namespace Bit.Infrastructure.EntityFramework.Repositories; public class OrganizationRepository : Repository, IOrganizationRepository { - private readonly ILogger _logger; + protected readonly ILogger _logger; public OrganizationRepository( IServiceScopeFactory serviceScopeFactory, diff --git a/src/Infrastructure.EntityFramework/Configurations/PlayItemEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Configurations/PlayItemEntityTypeConfiguration.cs new file mode 100644 index 0000000000..91b26e5be4 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Configurations/PlayItemEntityTypeConfiguration.cs @@ -0,0 +1,46 @@ +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Configurations; + +public class PlayItemEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(pd => pd.Id) + .ValueGeneratedNever(); + + builder + .HasIndex(pd => pd.PlayId) + .IsClustered(false); + + builder + .HasIndex(pd => pd.UserId) + .IsClustered(false); + + builder + .HasIndex(pd => pd.OrganizationId) + .IsClustered(false); + + builder + .HasOne(pd => pd.User) + .WithMany() + .HasForeignKey(pd => pd.UserId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .HasOne(pd => pd.Organization) + .WithMany() + .HasForeignKey(pd => pd.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + builder + .ToTable(nameof(PlayItem)) + .HasCheckConstraint( + "CK_PlayItem_UserOrOrganization", + "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)" + ); + } +} diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 3c35df2a82..320cb9436d 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -88,6 +88,7 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure.EntityFramework/Models/PlayItem.cs b/src/Infrastructure.EntityFramework/Models/PlayItem.cs new file mode 100644 index 0000000000..3aafebd555 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Models/PlayItem.cs @@ -0,0 +1,19 @@ +#nullable enable + +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class PlayItem : Core.Entities.PlayItem +{ + public virtual User? User { get; set; } + public virtual AdminConsole.Models.Organization? Organization { get; set; } +} + +public class PlayItemMapperProfile : Profile +{ + public PlayItemMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index b748a26db2..7b67a63912 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -57,6 +57,7 @@ public class DatabaseContext : DbContext public DbSet OrganizationApiKeys { get; set; } public DbSet OrganizationSponsorships { get; set; } public DbSet OrganizationConnections { get; set; } + public DbSet PlayItem { get; set; } public DbSet OrganizationIntegrations { get; set; } public DbSet OrganizationIntegrationConfigurations { get; set; } public DbSet OrganizationUsers { get; set; } diff --git a/src/Infrastructure.EntityFramework/Repositories/PlayItemRepository.cs b/src/Infrastructure.EntityFramework/Repositories/PlayItemRepository.cs new file mode 100644 index 0000000000..c8cf207b43 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/PlayItemRepository.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Bit.Infrastructure.EntityFramework.Repositories; + +public class PlayItemRepository : Repository, IPlayItemRepository +{ + public PlayItemRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PlayItem) + { } + + public async Task> GetByPlayIdAsync(string playId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var playItemEntities = await GetDbSet(dbContext) + .Where(pd => pd.PlayId == playId) + .ToListAsync(); + return Mapper.Map>(playItemEntities); + } + } + + public async Task DeleteByPlayIdAsync(string playId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entities = await GetDbSet(dbContext) + .Where(pd => pd.PlayId == playId) + .ToListAsync(); + + dbContext.PlayItem.RemoveRange(entities); + await dbContext.SaveChangesAsync(); + } + } +} diff --git a/src/SharedWeb/Play/PlayServiceCollectionExtensions.cs b/src/SharedWeb/Play/PlayServiceCollectionExtensions.cs new file mode 100644 index 0000000000..2611d58bfb --- /dev/null +++ b/src/SharedWeb/Play/PlayServiceCollectionExtensions.cs @@ -0,0 +1,30 @@ +using Bit.Core.Repositories; +using Bit.SharedWeb.Play.Repositories; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.SharedWeb.Play; + +public static class PlayServiceCollectionExtensions +{ + /// + /// Adds PlayId tracking decorators for User and Organization repositories using Dapper implementations. + /// This replaces the standard repository implementations with tracking versions + /// that record created entities for test data cleanup. Only call when TestPlayIdTrackingEnabled is true. + /// + public static void AddPlayIdTrackingDapperRepositories(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } + + /// + /// Adds PlayId tracking decorators for User and Organization repositories using EntityFramework implementations. + /// This replaces the standard repository implementations with tracking versions + /// that record created entities for test data cleanup. Only call when TestPlayIdTrackingEnabled is true. + /// + public static void AddPlayIdTrackingEFRepositories(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/src/SharedWeb/Play/Repositories/DapperTestOrganizationTrackingOrganizationRepository.cs b/src/SharedWeb/Play/Repositories/DapperTestOrganizationTrackingOrganizationRepository.cs new file mode 100644 index 0000000000..0d11d9c5eb --- /dev/null +++ b/src/SharedWeb/Play/Repositories/DapperTestOrganizationTrackingOrganizationRepository.cs @@ -0,0 +1,32 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Microsoft.Extensions.Logging; + +namespace Bit.SharedWeb.Play.Repositories; + +/// +/// Dapper decorator around the that tracks +/// created Organizations for seeding. +/// +public class DapperTestOrganizationTrackingOrganizationRepository : OrganizationRepository +{ + private readonly IPlayItemService _playItemService; + + public DapperTestOrganizationTrackingOrganizationRepository( + IPlayItemService playItemService, + GlobalSettings globalSettings, + ILogger logger) + : base(globalSettings, logger) + { + _playItemService = playItemService; + } + + public override async Task CreateAsync(Organization obj) + { + var createdOrganization = await base.CreateAsync(obj); + await _playItemService.Record(createdOrganization); + return createdOrganization; + } +} diff --git a/src/SharedWeb/Play/Repositories/DapperTestUserTrackingUserRepository.cs b/src/SharedWeb/Play/Repositories/DapperTestUserTrackingUserRepository.cs new file mode 100644 index 0000000000..97954c0348 --- /dev/null +++ b/src/SharedWeb/Play/Repositories/DapperTestUserTrackingUserRepository.cs @@ -0,0 +1,33 @@ +using Bit.Core.Entities; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Microsoft.AspNetCore.DataProtection; + +namespace Bit.SharedWeb.Play.Repositories; + +/// +/// Dapper decorator around the that tracks +/// created Users for seeding. +/// +public class DapperTestUserTrackingUserRepository : UserRepository +{ + private readonly IPlayItemService _playItemService; + + public DapperTestUserTrackingUserRepository( + IPlayItemService playItemService, + GlobalSettings globalSettings, + IDataProtectionProvider dataProtectionProvider) + : base(globalSettings, dataProtectionProvider) + { + _playItemService = playItemService; + } + + public override async Task CreateAsync(User user) + { + var createdUser = await base.CreateAsync(user); + + await _playItemService.Record(createdUser); + return createdUser; + } +} diff --git a/src/SharedWeb/Play/Repositories/EFTestOrganizationTrackingOrganizationRepository.cs b/src/SharedWeb/Play/Repositories/EFTestOrganizationTrackingOrganizationRepository.cs new file mode 100644 index 0000000000..fdf0e4d685 --- /dev/null +++ b/src/SharedWeb/Play/Repositories/EFTestOrganizationTrackingOrganizationRepository.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using Bit.Core.Services; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Bit.SharedWeb.Play.Repositories; + +/// +/// EntityFramework decorator around the that tracks +/// created Organizations for seeding. +/// +public class EFTestOrganizationTrackingOrganizationRepository : OrganizationRepository +{ + private readonly IPlayItemService _playItemService; + + public EFTestOrganizationTrackingOrganizationRepository( + IPlayItemService playItemService, + IServiceScopeFactory serviceScopeFactory, + IMapper mapper, + ILogger logger) + : base(serviceScopeFactory, mapper, logger) + { + _playItemService = playItemService; + } + + public override async Task CreateAsync(Core.AdminConsole.Entities.Organization organization) + { + var createdOrganization = await base.CreateAsync(organization); + await _playItemService.Record(createdOrganization); + return createdOrganization; + } +} diff --git a/src/SharedWeb/Play/Repositories/EFTestUserTrackingUserRepository.cs b/src/SharedWeb/Play/Repositories/EFTestUserTrackingUserRepository.cs new file mode 100644 index 0000000000..bafcd17ba0 --- /dev/null +++ b/src/SharedWeb/Play/Repositories/EFTestUserTrackingUserRepository.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Bit.Core.Services; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.SharedWeb.Play.Repositories; + +/// +/// EntityFramework decorator around the that tracks +/// created Users for seeding. +/// +public class EFTestUserTrackingUserRepository : UserRepository +{ + private readonly IPlayItemService _playItemService; + + public EFTestUserTrackingUserRepository( + IPlayItemService playItemService, + IServiceScopeFactory serviceScopeFactory, + IMapper mapper) + : base(serviceScopeFactory, mapper) + { + _playItemService = playItemService; + } + + public override async Task CreateAsync(Core.Entities.User user) + { + var createdUser = await base.CreateAsync(user); + await _playItemService.Record(createdUser); + return createdUser; + } +} diff --git a/src/SharedWeb/Utilities/PlayIdMiddleware.cs b/src/SharedWeb/Utilities/PlayIdMiddleware.cs new file mode 100644 index 0000000000..c00ab2b657 --- /dev/null +++ b/src/SharedWeb/Utilities/PlayIdMiddleware.cs @@ -0,0 +1,41 @@ +using Bit.Core.Services; +using Microsoft.AspNetCore.Http; + +namespace Bit.SharedWeb.Utilities; + +/// +/// Middleware to extract the x-play-id header and set it in the PlayIdService. +/// +/// PlayId is used in testing infrastructure to track data created during automated testing and fa cilitate cleanup. +/// +/// +public sealed class PlayIdMiddleware(RequestDelegate next) +{ + private const int MaxPlayIdLength = 256; + + public async Task Invoke(HttpContext context, PlayIdService playIdService) + { + if (context.Request.Headers.TryGetValue("x-play-id", out var playId)) + { + var playIdValue = playId.ToString(); + + if (string.IsNullOrWhiteSpace(playIdValue)) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsJsonAsync(new { Error = "x-play-id header cannot be empty or whitespace" }); + return; + } + + if (playIdValue.Length > MaxPlayIdLength) + { + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsJsonAsync(new { Error = $"x-play-id header cannot exceed {MaxPlayIdLength} characters" }); + return; + } + + playIdService.PlayId = playIdValue; + } + + await next(context); + } +} diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 8f5dfdf3f4..1bb9cb6c7a 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -56,6 +56,7 @@ using Bit.Core.Vault; using Bit.Core.Vault.Services; using Bit.Infrastructure.Dapper; using Bit.Infrastructure.EntityFramework; +using Bit.SharedWeb.Play; using DnsClient; using Duende.IdentityModel; using LaunchDarkly.Sdk.Server; @@ -117,6 +118,40 @@ public static class ServiceCollectionExtensions return provider; } + /// + /// Registers test PlayId tracking services for test data management and cleanup. + /// This infrastructure is isolated to test environments and enables tracking of test-generated entities. + /// + public static void AddTestPlayIdTracking(this IServiceCollection services, GlobalSettings globalSettings) + { + if (globalSettings.TestPlayIdTrackingEnabled) + { + var (provider, _) = GetDatabaseProvider(globalSettings); + + // Include PlayIdService for tracking Play Ids in repositories + // We need the http context accessor to use the Singleton version, which pulls from the scoped version + services.AddHttpContextAccessor(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); + + // Replace standard repositories with PlayId tracking decorators + if (provider == SupportedDatabaseProviders.SqlServer) + { + services.AddPlayIdTrackingDapperRepositories(); + } + else + { + services.AddPlayIdTrackingEFRepositories(); + } + } + else + { + services.AddSingleton(); + } + } + public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings) { services.AddScoped(); @@ -522,6 +557,10 @@ public static class ServiceCollectionExtensions IWebHostEnvironment env, GlobalSettings globalSettings) { app.UseMiddleware(); + if (globalSettings.TestPlayIdTrackingEnabled) + { + app.UseMiddleware(); + } } public static void UseForwardedHeaders(this IApplicationBuilder app, IGlobalSettings globalSettings) diff --git a/src/Sql/dbo/Stored Procedures/PlayItem_Create.sql b/src/Sql/dbo/Stored Procedures/PlayItem_Create.sql new file mode 100644 index 0000000000..cf75d03d10 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PlayItem_Create.sql @@ -0,0 +1,27 @@ +CREATE PROCEDURE [dbo].[PlayItem_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @PlayId NVARCHAR(256), + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CreationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[PlayItem] + ( + [Id], + [PlayId], + [UserId], + [OrganizationId], + [CreationDate] + ) + VALUES + ( + @Id, + @PlayId, + @UserId, + @OrganizationId, + @CreationDate + ) +END diff --git a/src/Sql/dbo/Stored Procedures/PlayItem_DeleteByPlayId.sql b/src/Sql/dbo/Stored Procedures/PlayItem_DeleteByPlayId.sql new file mode 100644 index 0000000000..5e2a102e40 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PlayItem_DeleteByPlayId.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[PlayItem_DeleteByPlayId] + @PlayId NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[PlayItem] + WHERE + [PlayId] = @PlayId +END diff --git a/src/Sql/dbo/Stored Procedures/PlayItem_ReadByPlayId.sql b/src/Sql/dbo/Stored Procedures/PlayItem_ReadByPlayId.sql new file mode 100644 index 0000000000..29592c527b --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PlayItem_ReadByPlayId.sql @@ -0,0 +1,17 @@ +CREATE PROCEDURE [dbo].[PlayItem_ReadByPlayId] + @PlayId NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [PlayId], + [UserId], + [OrganizationId], + [CreationDate] + FROM + [dbo].[PlayItem] + WHERE + [PlayId] = @PlayId +END diff --git a/src/Sql/dbo/Tables/PlayItem.sql b/src/Sql/dbo/Tables/PlayItem.sql new file mode 100644 index 0000000000..fbf3d2e0dd --- /dev/null +++ b/src/Sql/dbo/Tables/PlayItem.sql @@ -0,0 +1,23 @@ +CREATE TABLE [dbo].[PlayItem] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [PlayId] NVARCHAR (256) NOT NULL, + [UserId] UNIQUEIDENTIFIER NULL, + [OrganizationId] UNIQUEIDENTIFIER NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_PlayItem] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_PlayItem_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE, + CONSTRAINT [FK_PlayItem_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE, + CONSTRAINT [CK_PlayItem_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL)) +); + +GO +CREATE NONCLUSTERED INDEX [IX_PlayItem_PlayId] + ON [dbo].[PlayItem]([PlayId] ASC); + +GO +CREATE NONCLUSTERED INDEX [IX_PlayItem_UserId] + ON [dbo].[PlayItem]([UserId] ASC); + +GO +CREATE NONCLUSTERED INDEX [IX_PlayItem_OrganizationId] + ON [dbo].[PlayItem]([OrganizationId] ASC); diff --git a/test/Common/AutoFixture/SutProvider.cs b/test/Common/AutoFixture/SutProvider.cs index e1b37a9827..295f6bc950 100644 --- a/test/Common/AutoFixture/SutProvider.cs +++ b/test/Common/AutoFixture/SutProvider.cs @@ -26,6 +26,7 @@ public class SutProvider : ISutProvider public TSut Sut { get; private set; } public Type SutType => typeof(TSut); + public IFixture Fixture => _fixture; public SutProvider() : this(new Fixture()) { } @@ -65,6 +66,19 @@ public class SutProvider : ISutProvider return this; } + /// + /// Creates and registers a dependency to be injected when the sut is created. + /// + /// The Dependency type to create + /// The (optional) parameter name to register the dependency under + /// The created dependency value + public TDep CreateDependency(string parameterName = "") + { + var dependency = _fixture.Create(); + SetDependency(dependency, parameterName); + return dependency; + } + /// /// Gets a dependency of the sut. Can only be called after the dependency has been set, either explicitly with /// or automatically with . diff --git a/test/Core.Test/Services/PlayIdServiceTests.cs b/test/Core.Test/Services/PlayIdServiceTests.cs new file mode 100644 index 0000000000..7e4977c7bb --- /dev/null +++ b/test/Core.Test/Services/PlayIdServiceTests.cs @@ -0,0 +1,211 @@ +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Services; + +[SutProviderCustomize] +public class PlayIdServiceTests +{ + [Theory] + [BitAutoData] + public void InPlay_WhenPlayIdSetAndDevelopment_ReturnsTrue( + string playId, + SutProvider sutProvider) + { + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Development); + sutProvider.Sut.PlayId = playId; + + var result = sutProvider.Sut.InPlay(out var resultPlayId); + + Assert.True(result); + Assert.Equal(playId, resultPlayId); + } + + [Theory] + [BitAutoData] + public void InPlay_WhenPlayIdSetButNotDevelopment_ReturnsFalse( + string playId, + SutProvider sutProvider) + { + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Production); + sutProvider.Sut.PlayId = playId; + + var result = sutProvider.Sut.InPlay(out var resultPlayId); + + Assert.False(result); + Assert.Equal(playId, resultPlayId); + } + + [Theory] + [BitAutoData((string?)null)] + [BitAutoData("")] + public void InPlay_WhenPlayIdNullOrEmptyAndDevelopment_ReturnsFalse( + string? playId, + SutProvider sutProvider) + { + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Development); + sutProvider.Sut.PlayId = playId; + + var result = sutProvider.Sut.InPlay(out var resultPlayId); + + Assert.False(result); + Assert.Empty(resultPlayId); + } + + [Theory] + [BitAutoData] + public void PlayId_CanGetAndSet(string playId) + { + var hostEnvironment = Substitute.For(); + var sut = new PlayIdService(hostEnvironment); + + sut.PlayId = playId; + + Assert.Equal(playId, sut.PlayId); + } +} + +[SutProviderCustomize] +public class NeverPlayIdServicesTests +{ + [Fact] + public void InPlay_ReturnsFalse() + { + var sut = new NeverPlayIdServices(); + + var result = sut.InPlay(out var playId); + + Assert.False(result); + Assert.Empty(playId); + } + + [Theory] + [InlineData("test-play-id")] + [InlineData(null)] + public void PlayId_SetterDoesNothing_GetterReturnsNull(string? value) + { + var sut = new NeverPlayIdServices(); + + sut.PlayId = value; + + Assert.Null(sut.PlayId); + } +} + +[SutProviderCustomize] +public class PlayIdSingletonServiceTests +{ + public static IEnumerable SutProvider() + { + var sutProvider = new SutProvider(); + var httpContext = sutProvider.CreateDependency(); + var serviceProvider = sutProvider.CreateDependency(); + var hostEnvironment = sutProvider.CreateDependency(); + var playIdService = new PlayIdService(hostEnvironment); + sutProvider.SetDependency(playIdService); + httpContext.RequestServices.Returns(serviceProvider); + serviceProvider.GetService().Returns(playIdService); + serviceProvider.GetRequiredService().Returns(playIdService); + sutProvider.CreateDependency().HttpContext.Returns(httpContext); + sutProvider.Create(); + return [[sutProvider]]; + } + + private void PrepHttpContext( + SutProvider sutProvider) + { + var httpContext = sutProvider.CreateDependency(); + var serviceProvider = sutProvider.CreateDependency(); + var PlayIdService = sutProvider.CreateDependency(); + httpContext.RequestServices.Returns(serviceProvider); + serviceProvider.GetRequiredService().Returns(PlayIdService); + sutProvider.GetDependency().HttpContext.Returns(httpContext); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void InPlay_WhenNoHttpContext_ReturnsFalse( + SutProvider sutProvider) + { + sutProvider.GetDependency().HttpContext.Returns((HttpContext?)null); + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Development); + + var result = sutProvider.Sut.InPlay(out var playId); + + Assert.False(result); + Assert.Empty(playId); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void InPlay_WhenNotDevelopment_ReturnsFalse( + SutProvider sutProvider, + string playIdValue) + { + var scopedPlayIdService = sutProvider.GetDependency(); + scopedPlayIdService.PlayId = playIdValue; + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Production); + + var result = sutProvider.Sut.InPlay(out var playId); + + Assert.False(result); + Assert.Empty(playId); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void InPlay_WhenDevelopmentAndHttpContextWithPlayId_ReturnsTrue( + SutProvider sutProvider, + string playIdValue) + { + sutProvider.GetDependency().PlayId = playIdValue; + sutProvider.GetDependency().EnvironmentName.Returns(Environments.Development); + + var result = sutProvider.Sut.InPlay(out var playId); + + Assert.True(result); + Assert.Equal(playIdValue, playId); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void PlayId_SetterSetsOnScopedService( + SutProvider sutProvider, + string playIdValue) + { + var scopedPlayIdService = sutProvider.GetDependency(); + + sutProvider.Sut.PlayId = playIdValue; + + Assert.Equal(playIdValue, scopedPlayIdService.PlayId); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void PlayId_WhenNoHttpContext_GetterReturnsNull( + SutProvider sutProvider) + { + sutProvider.GetDependency().HttpContext.Returns((HttpContext?)null); + + var result = sutProvider.Sut.PlayId; + + Assert.Null(result); + } + + [Theory] + [BitMemberAutoData(nameof(SutProvider))] + public void PlayId_WhenNoHttpContext_SetterDoesNotThrow( + SutProvider sutProvider, + string playIdValue) + { + sutProvider.GetDependency().HttpContext.Returns((HttpContext?)null); + + sutProvider.Sut.PlayId = playIdValue; + } +} diff --git a/test/Core.Test/Services/PlayItemServiceTests.cs b/test/Core.Test/Services/PlayItemServiceTests.cs new file mode 100644 index 0000000000..02e815be59 --- /dev/null +++ b/test/Core.Test/Services/PlayItemServiceTests.cs @@ -0,0 +1,143 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Services; + +[SutProviderCustomize] +public class PlayItemServiceTests +{ + [Theory] + [BitAutoData] + public async Task Record_User_WhenInPlay_RecordsPlayItem( + string playId, + User user, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .InPlay(out Arg.Any()) + .Returns(x => + { + x[0] = playId; + return true; + }); + + await sutProvider.Sut.Record(user); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Is(pd => + pd.PlayId == playId && + pd.UserId == user.Id && + pd.OrganizationId == null)); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Information, + Arg.Any(), + Arg.Is(o => o.ToString().Contains(user.Id.ToString()) && o.ToString().Contains(playId)), + null, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task Record_User_WhenNotInPlay_DoesNotRecordPlayItem( + User user, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .InPlay(out Arg.Any()) + .Returns(x => + { + x[0] = null; + return false; + }); + + await sutProvider.Sut.Record(user); + + await sutProvider.GetDependency() + .DidNotReceive() + .CreateAsync(Arg.Any()); + + sutProvider.GetDependency>() + .DidNotReceive() + .Log( + LogLevel.Information, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task Record_Organization_WhenInPlay_RecordsPlayItem( + string playId, + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .InPlay(out Arg.Any()) + .Returns(x => + { + x[0] = playId; + return true; + }); + + await sutProvider.Sut.Record(organization); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Is(pd => + pd.PlayId == playId && + pd.OrganizationId == organization.Id && + pd.UserId == null)); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Information, + Arg.Any(), + Arg.Is(o => o.ToString().Contains(organization.Id.ToString()) && o.ToString().Contains(playId)), + null, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task Record_Organization_WhenNotInPlay_DoesNotRecordPlayItem( + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .InPlay(out Arg.Any()) + .Returns(x => + { + x[0] = null; + return false; + }); + + await sutProvider.Sut.Record(organization); + + await sutProvider.GetDependency() + .DidNotReceive() + .CreateAsync(Arg.Any()); + + sutProvider.GetDependency>() + .DidNotReceive() + .Log( + LogLevel.Information, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } +} diff --git a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs index c458969748..00e3149bbf 100644 --- a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs +++ b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs @@ -128,7 +128,6 @@ public class DatabaseDataAttribute : DataAttribute private void AddDapperServices(IServiceCollection services, Database database) { - services.AddDapperRepositories(SelfHosted); var globalSettings = new GlobalSettings { DatabaseProvider = "sqlServer", @@ -141,6 +140,7 @@ public class DatabaseDataAttribute : DataAttribute UserRequestExpiration = TimeSpan.FromMinutes(15), } }; + services.AddDapperRepositories(SelfHosted); services.AddSingleton(globalSettings); services.AddSingleton(globalSettings); services.AddSingleton(database); @@ -160,7 +160,6 @@ public class DatabaseDataAttribute : DataAttribute private void AddEfServices(IServiceCollection services, Database database) { services.SetupEntityFramework(database.ConnectionString, database.Type); - services.AddPasswordManagerEFRepositories(SelfHosted); var globalSettings = new GlobalSettings { @@ -169,6 +168,7 @@ public class DatabaseDataAttribute : DataAttribute UserRequestExpiration = TimeSpan.FromMinutes(15), }, }; + services.AddPasswordManagerEFRepositories(SelfHosted); services.AddSingleton(globalSettings); services.AddSingleton(globalSettings); diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index 4b42f575a1..dbea807259 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -47,7 +47,7 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory /// public bool ManagesDatabase { get; set; } = true; - private readonly List> _configureTestServices = new(); + protected readonly List> _configureTestServices = new(); private readonly List> _configureAppConfiguration = new(); public void SubstituteService(Action mockService) diff --git a/test/SeederApi.IntegrationTest/HttpClientExtensions.cs b/test/SeederApi.IntegrationTest/HttpClientExtensions.cs new file mode 100644 index 0000000000..0fd7934208 --- /dev/null +++ b/test/SeederApi.IntegrationTest/HttpClientExtensions.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace Bit.SeederApi.IntegrationTest; + +public static class HttpClientExtensions +{ + /// + /// Sends a POST request with JSON content and attaches the x-play-id header. + /// + /// The type of the value to serialize. + /// The HTTP client. + /// The URI the request is sent to. + /// The value to serialize. + /// The play ID to attach as x-play-id header. + /// Options to control the behavior during serialization. + /// A cancellation token that can be used to cancel the operation. + /// The task object representing the asynchronous operation. + public static Task PostAsJsonAsync( + this HttpClient client, + [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, + TValue value, + string playId, + JsonSerializerOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(client); + + if (string.IsNullOrWhiteSpace(playId)) + { + throw new ArgumentException("Play ID cannot be null or whitespace.", nameof(playId)); + } + + var content = JsonContent.Create(value, mediaType: null, options); + content.Headers.Remove("x-play-id"); + content.Headers.Add("x-play-id", playId); + + return client.PostAsync(requestUri, content, cancellationToken); + } +} diff --git a/test/SeederApi.IntegrationTest/QueryControllerTest.cs b/test/SeederApi.IntegrationTest/QueryControllerTest.cs new file mode 100644 index 0000000000..571181e49f --- /dev/null +++ b/test/SeederApi.IntegrationTest/QueryControllerTest.cs @@ -0,0 +1,75 @@ +using System.Net; +using Bit.SeederApi.Models.Request; +using Xunit; + +namespace Bit.SeederApi.IntegrationTest; + +public class QueryControllerTests : IClassFixture, IAsyncLifetime +{ + private readonly HttpClient _client; + private readonly SeederApiApplicationFactory _factory; + + public QueryControllerTests(SeederApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + } + + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } + + [Fact] + public async Task QueryEndpoint_WithValidQueryAndArguments_ReturnsOk() + { + var testEmail = $"emergency-test-{Guid.NewGuid()}@bitwarden.com"; + + var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel + { + Template = "EmergencyAccessInviteQuery", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }); + + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + Assert.NotNull(result); + + var urls = System.Text.Json.JsonSerializer.Deserialize>(result); + Assert.NotNull(urls); + // For a non-existent email, we expect an empty list + Assert.Empty(urls); + } + + [Fact] + public async Task QueryEndpoint_WithInvalidQueryName_ReturnsNotFound() + { + var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel + { + Template = "NonExistentQuery", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = "test@example.com" }) + }); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task QueryEndpoint_WithMissingRequiredField_ReturnsBadRequest() + { + // EmergencyAccessInviteQuery requires 'email' field + var response = await _client.PostAsJsonAsync("/query", new QueryRequestModel + { + Template = "EmergencyAccessInviteQuery", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { wrongField = "value" }) + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } +} diff --git a/test/SeederApi.IntegrationTest/SeedControllerTest.cs b/test/SeederApi.IntegrationTest/SeedControllerTest.cs new file mode 100644 index 0000000000..1d081d019e --- /dev/null +++ b/test/SeederApi.IntegrationTest/SeedControllerTest.cs @@ -0,0 +1,222 @@ +using System.Net; +using Bit.SeederApi.Models.Request; +using Bit.SeederApi.Models.Response; +using Xunit; + +namespace Bit.SeederApi.IntegrationTest; + +public class SeedControllerTests : IClassFixture, IAsyncLifetime +{ + private readonly HttpClient _client; + private readonly SeederApiApplicationFactory _factory; + + public SeedControllerTests(SeederApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + } + + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + // Clean up any seeded data after each test + await _client.DeleteAsync("/seed"); + _client.Dispose(); + } + + [Fact] + public async Task SeedEndpoint_WithValidScene_ReturnsOk() + { + var testEmail = $"seed-test-{Guid.NewGuid()}@bitwarden.com"; + var playId = Guid.NewGuid().ToString(); + + var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, playId); + + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result); + Assert.NotNull(result.MangleMap); + Assert.Null(result.Result); + } + + [Fact] + public async Task SeedEndpoint_WithInvalidSceneName_ReturnsNotFound() + { + var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "NonExistentScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = "test@example.com" }) + }); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task SeedEndpoint_WithMissingRequiredField_ReturnsBadRequest() + { + // SingleUserScene requires 'email' field + var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { wrongField = "value" }) + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task DeleteEndpoint_WithValidPlayId_ReturnsOk() + { + var testEmail = $"delete-test-{Guid.NewGuid()}@bitwarden.com"; + var playId = Guid.NewGuid().ToString(); + + var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, playId); + + seedResponse.EnsureSuccessStatusCode(); + var seedResult = await seedResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(seedResult); + + var deleteResponse = await _client.DeleteAsync($"/seed/{playId}"); + deleteResponse.EnsureSuccessStatusCode(); + } + + [Fact] + public async Task DeleteEndpoint_WithInvalidPlayId_ReturnsOk() + { + // DestroyRecipe is idempotent - returns null for non-existent play IDs + var nonExistentPlayId = Guid.NewGuid().ToString(); + var response = await _client.DeleteAsync($"/seed/{nonExistentPlayId}"); + + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal($$"""{"playId":"{{nonExistentPlayId}}"}""", content); + } + + [Fact] + public async Task DeleteBatchEndpoint_WithValidPlayIds_ReturnsOk() + { + // Create multiple seeds with different play IDs + var playIds = new List(); + for (var i = 0; i < 3; i++) + { + var playId = Guid.NewGuid().ToString(); + playIds.Add(playId); + + var testEmail = $"batch-test-{Guid.NewGuid()}@bitwarden.com"; + var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, playId); + + seedResponse.EnsureSuccessStatusCode(); + var seedResult = await seedResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(seedResult); + } + + // Delete them in batch + var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch") + { + Content = JsonContent.Create(playIds) + }; + var deleteResponse = await _client.SendAsync(request); + deleteResponse.EnsureSuccessStatusCode(); + + var result = await deleteResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal("Batch delete completed successfully", result.Message); + } + + [Fact] + public async Task DeleteBatchEndpoint_WithSomeInvalidIds_ReturnsOk() + { + // DestroyRecipe is idempotent - batch delete succeeds even with non-existent IDs + // Create one valid seed with a play ID + var validPlayId = Guid.NewGuid().ToString(); + var testEmail = $"batch-partial-test-{Guid.NewGuid()}@bitwarden.com"; + + var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, validPlayId); + + seedResponse.EnsureSuccessStatusCode(); + var seedResult = await seedResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(seedResult); + + // Try to delete with mix of valid and invalid IDs + var playIds = new List { validPlayId, Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }; + var request = new HttpRequestMessage(HttpMethod.Delete, "/seed/batch") + { + Content = JsonContent.Create(playIds) + }; + var deleteResponse = await _client.SendAsync(request); + + deleteResponse.EnsureSuccessStatusCode(); + var result = await deleteResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal("Batch delete completed successfully", result.Message); + } + + [Fact] + public async Task DeleteAllEndpoint_DeletesAllSeededData() + { + // Create multiple seeds + for (var i = 0; i < 2; i++) + { + var playId = Guid.NewGuid().ToString(); + var testEmail = $"deleteall-test-{Guid.NewGuid()}@bitwarden.com"; + + var seedResponse = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, playId); + + seedResponse.EnsureSuccessStatusCode(); + } + + // Delete all + var deleteResponse = await _client.DeleteAsync("/seed"); + Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode); + } + + [Fact] + public async Task SeedEndpoint_VerifyResponseContainsMangleMapAndResult() + { + var testEmail = $"verify-response-{Guid.NewGuid()}@bitwarden.com"; + var playId = Guid.NewGuid().ToString(); + + var response = await _client.PostAsJsonAsync("/seed", new SeedRequestModel + { + Template = "SingleUserScene", + Arguments = System.Text.Json.JsonSerializer.SerializeToElement(new { email = testEmail }) + }, playId); + + response.EnsureSuccessStatusCode(); + var jsonString = await response.Content.ReadAsStringAsync(); + + // Verify the response contains MangleMap and Result fields + Assert.Contains("mangleMap", jsonString, StringComparison.OrdinalIgnoreCase); + Assert.Contains("result", jsonString, StringComparison.OrdinalIgnoreCase); + } + + private class BatchDeleteResponse + { + public string? Message { get; set; } + } +} diff --git a/test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csproj b/test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csproj new file mode 100644 index 0000000000..a4709ea58a --- /dev/null +++ b/test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csproj @@ -0,0 +1,29 @@ + + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + %(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + + + diff --git a/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs new file mode 100644 index 0000000000..6d815b03ea --- /dev/null +++ b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs @@ -0,0 +1,18 @@ +using Bit.Core.Services; +using Bit.IntegrationTestCommon; +using Bit.IntegrationTestCommon.Factories; + +namespace Bit.SeederApi.IntegrationTest; + +public class SeederApiApplicationFactory : WebApplicationFactoryBase +{ + public SeederApiApplicationFactory() + { + TestDatabase = new SqliteTestDatabase(); + _configureTestServices.Add(serviceCollection => + { + serviceCollection.AddSingleton(); + serviceCollection.AddHttpContextAccessor(); + }); + } +} diff --git a/test/SharedWeb.Test/PlayIdMiddlewareTests.cs b/test/SharedWeb.Test/PlayIdMiddlewareTests.cs new file mode 100644 index 0000000000..f9ed23c36f --- /dev/null +++ b/test/SharedWeb.Test/PlayIdMiddlewareTests.cs @@ -0,0 +1,102 @@ +using Bit.Core.Services; +using Bit.SharedWeb.Utilities; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; +using NSubstitute; + +namespace SharedWeb.Test; + +public class PlayIdMiddlewareTests +{ + private readonly PlayIdService _playIdService; + private readonly RequestDelegate _next; + private readonly PlayIdMiddleware _middleware; + + public PlayIdMiddlewareTests() + { + var hostEnvironment = Substitute.For(); + hostEnvironment.EnvironmentName.Returns(Environments.Development); + + _playIdService = new PlayIdService(hostEnvironment); + _next = Substitute.For(); + _middleware = new PlayIdMiddleware(_next); + } + + [Fact] + public async Task Invoke_WithValidPlayId_SetsPlayIdAndCallsNext() + { + var context = new DefaultHttpContext(); + context.Request.Headers["x-play-id"] = "test-play-id"; + + await _middleware.Invoke(context, _playIdService); + + Assert.Equal("test-play-id", _playIdService.PlayId); + await _next.Received(1).Invoke(context); + } + + [Fact] + public async Task Invoke_WithoutPlayIdHeader_CallsNext() + { + var context = new DefaultHttpContext(); + + await _middleware.Invoke(context, _playIdService); + + Assert.Null(_playIdService.PlayId); + await _next.Received(1).Invoke(context); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t")] + public async Task Invoke_WithEmptyOrWhitespacePlayId_Returns400(string playId) + { + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + context.Request.Headers["x-play-id"] = playId; + + await _middleware.Invoke(context, _playIdService); + + Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); + await _next.DidNotReceive().Invoke(context); + } + + [Fact] + public async Task Invoke_WithPlayIdExceedingMaxLength_Returns400() + { + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + var longPlayId = new string('a', 257); // Exceeds 256 character limit + context.Request.Headers["x-play-id"] = longPlayId; + + await _middleware.Invoke(context, _playIdService); + + Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); + await _next.DidNotReceive().Invoke(context); + } + + [Fact] + public async Task Invoke_WithPlayIdAtMaxLength_SetsPlayIdAndCallsNext() + { + var context = new DefaultHttpContext(); + var maxLengthPlayId = new string('a', 256); // Exactly 256 characters + context.Request.Headers["x-play-id"] = maxLengthPlayId; + + await _middleware.Invoke(context, _playIdService); + + Assert.Equal(maxLengthPlayId, _playIdService.PlayId); + await _next.Received(1).Invoke(context); + } + + [Fact] + public async Task Invoke_WithSpecialCharactersInPlayId_SetsPlayIdAndCallsNext() + { + var context = new DefaultHttpContext(); + context.Request.Headers["x-play-id"] = "test-play_id.123"; + + await _middleware.Invoke(context, _playIdService); + + Assert.Equal("test-play_id.123", _playIdService.PlayId); + await _next.Received(1).Invoke(context); + } +} diff --git a/util/Migrator/DbScripts/2026-01-08_00_CreatePlayItem.sql b/util/Migrator/DbScripts/2026-01-08_00_CreatePlayItem.sql new file mode 100644 index 0000000000..789538cb1c --- /dev/null +++ b/util/Migrator/DbScripts/2026-01-08_00_CreatePlayItem.sql @@ -0,0 +1,90 @@ +-- Create PlayItem table +IF OBJECT_ID('dbo.PlayItem') IS NULL +BEGIN + CREATE TABLE [dbo].[PlayItem] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [PlayId] NVARCHAR (256) NOT NULL, + [UserId] UNIQUEIDENTIFIER NULL, + [OrganizationId] UNIQUEIDENTIFIER NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_PlayItem] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_PlayItem_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE, + CONSTRAINT [FK_PlayItem_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE, + CONSTRAINT [CK_PlayItem_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL)) + ); + + CREATE NONCLUSTERED INDEX [IX_PlayItem_PlayId] + ON [dbo].[PlayItem]([PlayId] ASC); + + CREATE NONCLUSTERED INDEX [IX_PlayItem_UserId] + ON [dbo].[PlayItem]([UserId] ASC); + + CREATE NONCLUSTERED INDEX [IX_PlayItem_OrganizationId] + ON [dbo].[PlayItem]([OrganizationId] ASC); +END +GO + +-- Create PlayItem_Create stored procedure +CREATE OR ALTER PROCEDURE [dbo].[PlayItem_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @PlayId NVARCHAR(256), + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CreationDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[PlayItem] + ( + [Id], + [PlayId], + [UserId], + [OrganizationId], + [CreationDate] + ) + VALUES + ( + @Id, + @PlayId, + @UserId, + @OrganizationId, + @CreationDate + ) +END +GO + +-- Create PlayItem_ReadByPlayId stored procedure +CREATE OR ALTER PROCEDURE [dbo].[PlayItem_ReadByPlayId] + @PlayId NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [PlayId], + [UserId], + [OrganizationId], + [CreationDate] + FROM + [dbo].[PlayItem] + WHERE + [PlayId] = @PlayId +END +GO + +-- Create PlayItem_DeleteByPlayId stored procedure +CREATE OR ALTER PROCEDURE [dbo].[PlayItem_DeleteByPlayId] + @PlayId NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[PlayItem] + WHERE + [PlayId] = @PlayId +END +GO diff --git a/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.Designer.cs b/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.Designer.cs new file mode 100644 index 0000000000..779f8a06f2 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.Designer.cs @@ -0,0 +1,3502 @@ +// +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("20260108193951_CreatePlayItem")] + partial class CreatePlayItem + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("tinyint(1)"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePhishingBlocker") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("ApplicationCount") + .HasColumnType("int"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalApplicationCount") + .HasColumnType("int"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalMemberCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordCount") + .HasColumnType("int"); + + b.Property("MemberAtRiskCount") + .HasColumnType("int"); + + b.Property("MemberCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("PasswordCount") + .HasColumnType("int"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("AuthType") + .HasColumnType("tinyint unsigned"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SecurityState") + .HasColumnType("longtext"); + + b.Property("SecurityVersion") + .HasColumnType("int"); + + b.Property("SignedPublicKey") + .HasColumnType("longtext"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SignatureAlgorithm") + .HasColumnType("tinyint unsigned"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("EditorServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VersionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Archives") + .HasColumnType("longtext"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.cs b/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.cs new file mode 100644 index 0000000000..d1b69417d4 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20260108193951_CreatePlayItem.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class CreatePlayItem : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PlayItem", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + PlayId = table.Column(type: "varchar(256)", maxLength: 256, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + UserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + CreationDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayItem", x => x.Id); + table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + table.ForeignKey( + name: "FK_PlayItem_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PlayItem_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_OrganizationId", + table: "PlayItem", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_PlayId", + table: "PlayItem", + column: "PlayId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_UserId", + table: "PlayItem", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PlayItem"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index b0b88670a1..dda8d5d179 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -282,71 +282,6 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("Organization", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Configuration") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("OrganizationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("Configuration") - .HasColumnType("longtext"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("EventType") - .HasColumnType("int"); - - b.Property("Filters") - .HasColumnType("longtext"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("char(36)"); - - b.Property("RevisionDate") - .HasColumnType("datetime(6)"); - - b.Property("Template") - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => { b.Property("Id") @@ -626,8 +561,8 @@ namespace Bit.MySqlMigrations.Migrations b.Property("Type") .HasColumnType("tinyint unsigned"); - b.Property("WaitTimeDays") - .HasColumnType("int"); + b.Property("WaitTimeDays") + .HasColumnType("smallint"); b.HasKey("Id"); @@ -1015,6 +950,71 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("OrganizationApplication", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.Property("Id") @@ -1627,6 +1627,42 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("OrganizationUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.Property("Id") @@ -2607,28 +2643,6 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -2807,6 +2821,28 @@ namespace Bit.MySqlMigrations.Migrations b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -3003,6 +3039,23 @@ namespace Bit.MySqlMigrations.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.Designer.cs b/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.Designer.cs new file mode 100644 index 0000000000..0586bd8a23 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.Designer.cs @@ -0,0 +1,3508 @@ +// +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("20260108193909_CreatePlayItem")] + partial class CreatePlayItem + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("SyncSeats") + .HasColumnType("boolean"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePhishingBlocker") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("ApplicationCount") + .HasColumnType("integer"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalApplicationCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordCount") + .HasColumnType("integer"); + + b.Property("MemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("MemberCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("PasswordCount") + .HasColumnType("integer"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("AuthType") + .HasColumnType("smallint"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SecurityState") + .HasColumnType("text"); + + b.Property("SecurityVersion") + .HasColumnType("integer"); + + b.Property("SignedPublicKey") + .HasColumnType("text"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SignatureAlgorithm") + .HasColumnType("smallint"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("EditorServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Archives") + .HasColumnType("text"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.cs b/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.cs new file mode 100644 index 0000000000..1708ab301e --- /dev/null +++ b/util/PostgresMigrations/Migrations/20260108193909_CreatePlayItem.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class CreatePlayItem : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PlayItem", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + PlayId = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + UserId = table.Column(type: "uuid", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayItem", x => x.Id); + table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + table.ForeignKey( + name: "FK_PlayItem_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PlayItem_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_OrganizationId", + table: "PlayItem", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_PlayId", + table: "PlayItem", + column: "PlayId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_UserId", + table: "PlayItem", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PlayItem"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 2a0b91e25d..f5419fa319 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -285,71 +285,6 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("Organization", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Configuration") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("OrganizationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("Configuration") - .HasColumnType("text"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("EventType") - .HasColumnType("integer"); - - b.Property("Filters") - .HasColumnType("text"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("uuid"); - - b.Property("RevisionDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Template") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => { b.Property("Id") @@ -629,8 +564,8 @@ namespace Bit.PostgresMigrations.Migrations b.Property("Type") .HasColumnType("smallint"); - b.Property("WaitTimeDays") - .HasColumnType("integer"); + b.Property("WaitTimeDays") + .HasColumnType("smallint"); b.HasKey("Id"); @@ -1020,6 +955,71 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("OrganizationApplication", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.Property("Id") @@ -1632,6 +1632,42 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("OrganizationUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.Property("Id") @@ -2613,28 +2649,6 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -2813,6 +2827,28 @@ namespace Bit.PostgresMigrations.Migrations b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -3009,6 +3045,23 @@ namespace Bit.PostgresMigrations.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") diff --git a/util/RustSdk/RustSdk.csproj b/util/RustSdk/RustSdk.csproj index ba16a55661..14cc017365 100644 --- a/util/RustSdk/RustSdk.csproj +++ b/util/RustSdk/RustSdk.csproj @@ -10,6 +10,14 @@ + + + + + + + Always true @@ -18,23 +26,36 @@ Always true - runtimes/linux-x64/native/libsdk.dylib + runtimes/linux-x64/native/libsdk.so Always true - runtimes/windows-x64/native/libsdk.dylib + runtimes/windows-x64/native/libsdk.dll - - - + + + + Always + true + runtimes/osx-arm64/native/libsdk.dylib + + + Always + true + runtimes/linux-x64/native/libsdk.so + + + Always + true + runtimes/windows-x64/native/libsdk.dll + diff --git a/util/RustSdk/rust-toolchain.toml b/util/RustSdk/rust-toolchain.toml new file mode 100644 index 0000000000..b8889a3bb3 --- /dev/null +++ b/util/RustSdk/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.87.0" diff --git a/util/Seeder/Factories/OrganizationSeeder.cs b/util/Seeder/Factories/OrganizationSeeder.cs index 012661501f..3aac87d400 100644 --- a/util/Seeder/Factories/OrganizationSeeder.cs +++ b/util/Seeder/Factories/OrganizationSeeder.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Bit.Infrastructure.EntityFramework.Models; namespace Bit.Seeder.Factories; diff --git a/util/Seeder/Factories/UserSeeder.cs b/util/Seeder/Factories/UserSeeder.cs index b24f8273b9..dd5fc159c0 100644 --- a/util/Seeder/Factories/UserSeeder.cs +++ b/util/Seeder/Factories/UserSeeder.cs @@ -1,13 +1,58 @@ -using Bit.Core.Enums; -using Bit.Infrastructure.EntityFramework.Models; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Utilities; using Bit.RustSDK; using Microsoft.AspNetCore.Identity; namespace Bit.Seeder.Factories; -public class UserSeeder +public struct UserData { - public static User CreateUser(string email) + public string Email; + public Guid Id; + public string? Key; + public string? PublicKey; + public string? PrivateKey; + public string? ApiKey; + public KdfType Kdf; + public int KdfIterations; +} + +public class UserSeeder(RustSdkService sdkService, IPasswordHasher passwordHasher, MangleId mangleId) +{ + private string MangleEmail(string email) + { + return $"{mangleId}+{email}"; + } + + public User CreateUser(string email, bool emailVerified = false, bool premium = false) + { + email = MangleEmail(email); + var keys = sdkService.GenerateUserKeys(email, "asdfasdfasdf"); + + var user = new User + { + Id = CoreHelpers.GenerateComb(), + Email = email, + EmailVerified = emailVerified, + MasterPassword = null, + SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609", + Key = keys.EncryptedUserKey, + PublicKey = keys.PublicKey, + PrivateKey = keys.PrivateKey, + Premium = premium, + ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR", + + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = 5_000, + }; + + user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash); + + return user; + } + + public static User CreateUserNoMangle(string email) { return new User { @@ -25,28 +70,35 @@ public class UserSeeder }; } - public static (User user, string userKey) CreateSdkUser(IPasswordHasher passwordHasher, string email) + public Dictionary GetMangleMap(User user, UserData expectedUserData) { - var nativeService = RustSdkServiceFactory.CreateSingleton(); - var keys = nativeService.GenerateUserKeys(email, "asdfasdfasdf"); - - var user = new User + var mangleMap = new Dictionary { - Id = Guid.NewGuid(), - Email = email, - MasterPassword = null, - SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609", - Key = keys.EncryptedUserKey, - PublicKey = keys.PublicKey, - PrivateKey = keys.PrivateKey, - ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR", - - Kdf = KdfType.PBKDF2_SHA256, - KdfIterations = 5_000, + { expectedUserData.Email, MangleEmail(expectedUserData.Email) }, + { expectedUserData.Id.ToString(), user.Id.ToString() }, + { expectedUserData.Kdf.ToString(), user.Kdf.ToString() }, + { expectedUserData.KdfIterations.ToString(), user.KdfIterations.ToString() } }; + if (expectedUserData.Key != null) + { + mangleMap[expectedUserData.Key] = user.Key; + } - user.MasterPassword = passwordHasher.HashPassword(user, keys.MasterPasswordHash); + if (expectedUserData.PublicKey != null) + { + mangleMap[expectedUserData.PublicKey] = user.PublicKey; + } - return (user, keys.Key); + if (expectedUserData.PrivateKey != null) + { + mangleMap[expectedUserData.PrivateKey] = user.PrivateKey; + } + + if (expectedUserData.ApiKey != null) + { + mangleMap[expectedUserData.ApiKey] = user.ApiKey; + } + + return mangleMap; } } diff --git a/util/Seeder/IQuery.cs b/util/Seeder/IQuery.cs new file mode 100644 index 0000000000..adbcd8e59d --- /dev/null +++ b/util/Seeder/IQuery.cs @@ -0,0 +1,60 @@ +namespace Bit.Seeder; + +/// +/// Base interface for query operations in the seeding system. The base interface should not be used directly, rather use `IQuery<TRequest, TResult>`. +/// +/// +/// Queries are synchronous, read-only operations that retrieve data from the seeding context. +/// Unlike scenes which create data, queries fetch existing data based on request parameters. +/// They follow a type-safe pattern using generics to ensure proper request/response handling +/// while maintaining a common non-generic interface for dynamic invocation. +/// +public interface IQuery +{ + /// + /// Gets the type of request this query expects. + /// + /// The request type that this query can process. + Type GetRequestType(); + + /// + /// Executes the query based on the provided request object. + /// + /// The request object containing parameters for the query operation. + /// The query result data as an object. + object Execute(object request); +} + +/// +/// Generic query interface for synchronous, read-only operations with specific request and result types. +/// +/// The type of request object this query accepts. +/// The type of data this query returns. +/// +/// Use this interface when you need to retrieve existing data from the seeding context based on +/// specific request parameters. Queries are synchronous and do not modify data - they only read +/// and return information. The explicit interface implementations allow dynamic invocation while +/// maintaining type safety in the implementation. +/// +public interface IQuery : IQuery where TRequest : class where TResult : class +{ + /// + /// Executes the query based on the provided strongly-typed request and returns typed result data. + /// + /// The request object containing parameters for the query operation. + /// The typed query result data. + TResult Execute(TRequest request); + + /// + /// Gets the request type for this query. + /// + /// The type of TRequest. + Type IQuery.GetRequestType() => typeof(TRequest); + + /// + /// Adapts the non-generic Execute to the strongly-typed version. + /// + /// The request object to cast and process. + /// The typed result cast to object. + object IQuery.Execute(object request) => Execute((TRequest)request); +} diff --git a/util/Seeder/IScene.cs b/util/Seeder/IScene.cs new file mode 100644 index 0000000000..6f513973ba --- /dev/null +++ b/util/Seeder/IScene.cs @@ -0,0 +1,96 @@ +namespace Bit.Seeder; + +/// +/// Base interface for seeding operations. The base interface should not be used directly, rather use `IScene<Request>`. +/// +/// +/// Scenes are components in the seeding system that create and configure test data. They follow +/// a type-safe pattern using generics to ensure proper request/response handling while maintaining +/// a common non-generic interface for dynamic invocation. +/// +public interface IScene +{ + /// + /// Gets the type of request this scene expects. + /// + /// The request type that this scene can process. + Type GetRequestType(); + + /// + /// Seeds data based on the provided request object. + /// + /// The request object containing parameters for the seeding operation. + /// A scene result containing any returned data, mangle map, and entity tracking information. + Task> SeedAsync(object request); +} + +/// +/// Generic scene interface for seeding operations with a specific request type. Does not return a value beyond tracking entities and a mangle map. +/// +/// The type of request object this scene accepts. +/// +/// Use this interface when your scene needs to process a specific request type but doesn't need to +/// return any data beyond the standard mangle map for ID transformations and entity tracking. +/// The explicit interface implementations allow this scene to be invoked dynamically through the +/// base IScene interface while maintaining type safety in the implementation. +/// +public interface IScene : IScene where TRequest : class +{ + /// + /// Seeds data based on the provided strongly-typed request. + /// + /// The request object containing parameters for the seeding operation. + /// A scene result containing the mangle map and entity tracking information. + Task SeedAsync(TRequest request); + + /// + /// Gets the request type for this scene. + /// + /// The type of TRequest. + Type IScene.GetRequestType() => typeof(TRequest); + + /// + /// Adapts the non-generic SeedAsync to the strongly-typed version. + /// + /// The request object to cast and process. + /// A scene result wrapped as an object result. + async Task> IScene.SeedAsync(object request) + { + var result = await SeedAsync((TRequest)request); + return new SceneResult(mangleMap: result.MangleMap); + } +} + +/// +/// Generic scene interface for seeding operations with a specific request type that returns typed data. +/// +/// The type of request object this scene accepts. Must be a reference type. +/// The type of data this scene returns. Must be a reference type. +/// +/// Use this interface when your scene needs to return specific data that can be used by subsequent +/// scenes or test logic. The result is wrapped in a SceneResult that also includes the mangle map +/// and entity tracking information. The explicit interface implementations allow dynamic invocation +/// while preserving type safety in the implementation. +/// +public interface IScene : IScene where TRequest : class where TResult : class +{ + /// + /// Seeds data based on the provided strongly-typed request and returns typed result data. + /// + /// The request object containing parameters for the seeding operation. + /// A scene result containing the typed result data, mangle map, and entity tracking information. + Task> SeedAsync(TRequest request); + + /// + /// Gets the request type for this scene. + /// + /// The type of TRequest. + Type IScene.GetRequestType() => typeof(TRequest); + + /// + /// Adapts the non-generic SeedAsync to the strongly-typed version. + /// + /// The request object to cast and process. + /// A scene result with the typed result cast to object. + async Task> IScene.SeedAsync(object request) => (SceneResult)await SeedAsync((TRequest)request); +} diff --git a/util/Seeder/MangleId.cs b/util/Seeder/MangleId.cs new file mode 100644 index 0000000000..e1be47f4d2 --- /dev/null +++ b/util/Seeder/MangleId.cs @@ -0,0 +1,19 @@ +namespace Bit.Seeder; + +/// +/// Helper for generating unique identifier suffixes to prevent collisions in test data. +/// "Mangling" adds a random suffix to test data identifiers (usernames, emails, org names, etc.) +/// to ensure uniqueness across multiple test runs and parallel test executions. +/// +public class MangleId +{ + public readonly string Value; + + public MangleId() + { + // Generate a short random string (6 char) to use as the mangle ID + Value = Random.Shared.NextInt64().ToString("x").Substring(0, 8); + } + + public override string ToString() => Value; +} diff --git a/util/Seeder/Queries/EmergencyAccessInviteQuery.cs b/util/Seeder/Queries/EmergencyAccessInviteQuery.cs new file mode 100644 index 0000000000..95d96a9a50 --- /dev/null +++ b/util/Seeder/Queries/EmergencyAccessInviteQuery.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Auth.Models.Business.Tokenables; +using Bit.Core.Tokens; +using Bit.Infrastructure.EntityFramework.Repositories; + +namespace Bit.Seeder.Queries; + +/// +/// Retrieves all emergency access invite urls for the provided email. +/// +public class EmergencyAccessInviteQuery( + DatabaseContext db, + IDataProtectorTokenFactory dataProtectorTokenizer) + : IQuery> +{ + public class Request + { + [Required] + public required string Email { get; set; } + } + + public IEnumerable Execute(Request request) + { + var invites = db.EmergencyAccesses + .Where(ea => ea.Email == request.Email).ToList().Select(ea => + { + var token = dataProtectorTokenizer.Protect( + new EmergencyAccessInviteTokenable(ea, hoursTillExpiration: 1) + ); + return $"/accept-emergency?id={ea.Id}&name=Dummy&email={ea.Email}&token={token}"; + }); + + return invites; + } +} diff --git a/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs b/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs index 7678c3a9ce..87fcc1967b 100644 --- a/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs +++ b/util/Seeder/Recipes/OrganizationWithUsersRecipe.cs @@ -1,5 +1,5 @@ -using Bit.Core.Enums; -using Bit.Infrastructure.EntityFramework.Models; +using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Seeder.Factories; using LinqToDB.EntityFrameworkCore; @@ -12,14 +12,14 @@ public class OrganizationWithUsersRecipe(DatabaseContext db) { var seats = Math.Max(users + 1, 1000); var organization = OrganizationSeeder.CreateEnterprise(name, domain, seats); - var ownerUser = UserSeeder.CreateUser($"owner@{domain}"); + var ownerUser = UserSeeder.CreateUserNoMangle($"owner@{domain}"); var ownerOrgUser = organization.CreateOrganizationUser(ownerUser, OrganizationUserType.Owner, OrganizationUserStatusType.Confirmed); var additionalUsers = new List(); var additionalOrgUsers = new List(); for (var i = 0; i < users; i++) { - var additionalUser = UserSeeder.CreateUser($"user{i}@{domain}"); + var additionalUser = UserSeeder.CreateUserNoMangle($"user{i}@{domain}"); additionalUsers.Add(additionalUser); additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser, OrganizationUserType.User, usersStatus)); } diff --git a/util/Seeder/SceneResult.cs b/util/Seeder/SceneResult.cs new file mode 100644 index 0000000000..7ac004f55e --- /dev/null +++ b/util/Seeder/SceneResult.cs @@ -0,0 +1,28 @@ +namespace Bit.Seeder; + +/// +/// Helper for exposing a interface with a SeedAsync method. +/// +public class SceneResult(Dictionary mangleMap) + : SceneResult(result: null, mangleMap: mangleMap); + +/// +/// Generic result from executing a Scene. +/// Contains custom scene-specific data and a mangle map that maps magic strings from the +/// request to their mangled (collision-free) values inserted into the database. +/// +/// The type of custom result data returned by the scene. +public class SceneResult(TResult result, Dictionary mangleMap) +{ + public TResult Result { get; init; } = result; + public Dictionary MangleMap { get; init; } = mangleMap; + + public static explicit operator SceneResult(SceneResult v) + { + var result = v.Result; + + return result is null + ? new SceneResult(result: null, mangleMap: v.MangleMap) + : new SceneResult(result: result, mangleMap: v.MangleMap); + } +} diff --git a/util/Seeder/Scenes/SingleUserScene.cs b/util/Seeder/Scenes/SingleUserScene.cs new file mode 100644 index 0000000000..df941c7f59 --- /dev/null +++ b/util/Seeder/Scenes/SingleUserScene.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Repositories; +using Bit.Seeder.Factories; + +namespace Bit.Seeder.Scenes; + +/// +/// Creates a single user using the provided account details. +/// +public class SingleUserScene(UserSeeder userSeeder, IUserRepository userRepository) : IScene +{ + public class Request + { + [Required] + public required string Email { get; set; } + public bool EmailVerified { get; set; } = false; + public bool Premium { get; set; } = false; + } + + public async Task SeedAsync(Request request) + { + var user = userSeeder.CreateUser(request.Email, request.EmailVerified, request.Premium); + + await userRepository.CreateAsync(user); + + return new SceneResult(mangleMap: userSeeder.GetMangleMap(user, new UserData + { + Email = request.Email, + Id = user.Id, + Key = user.Key, + PublicKey = user.PublicKey, + PrivateKey = user.PrivateKey, + ApiKey = user.ApiKey, + Kdf = user.Kdf, + KdfIterations = user.KdfIterations, + })); + } +} diff --git a/util/Seeder/Seeder.csproj b/util/Seeder/Seeder.csproj index 4d7fbab767..fd6e26c1ee 100644 --- a/util/Seeder/Seeder.csproj +++ b/util/Seeder/Seeder.csproj @@ -12,10 +12,6 @@ false - - - - diff --git a/util/SeederApi/Commands/DestroyBatchScenesCommand.cs b/util/SeederApi/Commands/DestroyBatchScenesCommand.cs new file mode 100644 index 0000000000..50f6142a98 --- /dev/null +++ b/util/SeederApi/Commands/DestroyBatchScenesCommand.cs @@ -0,0 +1,36 @@ +using Bit.SeederApi.Commands.Interfaces; + +namespace Bit.SeederApi.Commands; + +public class DestroyBatchScenesCommand( + ILogger logger, + IDestroySceneCommand destroySceneCommand) : IDestroyBatchScenesCommand +{ + public async Task DestroyAsync(IEnumerable playIds) + { + var exceptions = new List(); + + var deleteTasks = playIds.Select(async playId => + { + try + { + await destroySceneCommand.DestroyAsync(playId); + } + catch (Exception ex) + { + lock (exceptions) + { + exceptions.Add(ex); + } + logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId); + } + }); + + await Task.WhenAll(deleteTasks); + + if (exceptions.Count > 0) + { + throw new AggregateException("One or more errors occurred while deleting seeded data", exceptions); + } + } +} diff --git a/util/SeederApi/Commands/DestroySceneCommand.cs b/util/SeederApi/Commands/DestroySceneCommand.cs new file mode 100644 index 0000000000..0e0f4edd6d --- /dev/null +++ b/util/SeederApi/Commands/DestroySceneCommand.cs @@ -0,0 +1,57 @@ +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.SeederApi.Commands.Interfaces; +using Bit.SeederApi.Services; + +namespace Bit.SeederApi.Commands; + +public class DestroySceneCommand( + DatabaseContext databaseContext, + ILogger logger, + IUserRepository userRepository, + IPlayItemRepository playItemRepository, + IOrganizationRepository organizationRepository) : IDestroySceneCommand +{ + public async Task DestroyAsync(string playId) + { + // Note, delete cascade will remove PlayItem entries + + var playItem = await playItemRepository.GetByPlayIdAsync(playId); + var userIds = playItem.Select(pd => pd.UserId).Distinct().ToList(); + var organizationIds = playItem.Select(pd => pd.OrganizationId).Distinct().ToList(); + + // Delete Users before Organizations to respect foreign key constraints + if (userIds.Count > 0) + { + var users = databaseContext.Users.Where(u => userIds.Contains(u.Id)); + await userRepository.DeleteManyAsync(users); + } + + if (organizationIds.Count > 0) + { + var organizations = databaseContext.Organizations.Where(o => organizationIds.Contains(o.Id)); + var aggregateException = new AggregateException(); + foreach (var org in organizations) + { + try + { + await organizationRepository.DeleteAsync(org); + } + catch (Exception ex) + { + aggregateException = new AggregateException(aggregateException, ex); + } + } + if (aggregateException.InnerExceptions.Count > 0) + { + throw new SceneExecutionException( + $"One or more errors occurred while deleting organizations for seed ID {playId}", + aggregateException); + } + } + + logger.LogInformation("Successfully destroyed seeded data with ID {PlayId}", playId); + + return new { PlayId = playId }; + } +} diff --git a/util/SeederApi/Commands/Interfaces/IDestroyBatchScenesCommand.cs b/util/SeederApi/Commands/Interfaces/IDestroyBatchScenesCommand.cs new file mode 100644 index 0000000000..ce43f26a54 --- /dev/null +++ b/util/SeederApi/Commands/Interfaces/IDestroyBatchScenesCommand.cs @@ -0,0 +1,14 @@ +namespace Bit.SeederApi.Commands.Interfaces; + +/// +/// Command for destroying multiple scenes in parallel. +/// +public interface IDestroyBatchScenesCommand +{ + /// + /// Destroys multiple scenes by their play IDs in parallel. + /// + /// The list of play IDs to destroy + /// Thrown when one or more scenes fail to destroy + Task DestroyAsync(IEnumerable playIds); +} diff --git a/util/SeederApi/Commands/Interfaces/IDestroySceneCommand.cs b/util/SeederApi/Commands/Interfaces/IDestroySceneCommand.cs new file mode 100644 index 0000000000..a3b0800bb2 --- /dev/null +++ b/util/SeederApi/Commands/Interfaces/IDestroySceneCommand.cs @@ -0,0 +1,15 @@ +namespace Bit.SeederApi.Commands.Interfaces; + +/// +/// Command for destroying data created by a single scene. +/// +public interface IDestroySceneCommand +{ + /// + /// Destroys data created by a scene using the seeded data ID. + /// + /// The ID of the seeded data to destroy + /// The result of the destroy operation + /// Thrown when there's an error destroying the seeded data + Task DestroyAsync(string playId); +} diff --git a/util/SeederApi/Controllers/InfoController.cs b/util/SeederApi/Controllers/InfoController.cs new file mode 100644 index 0000000000..de4a264ddb --- /dev/null +++ b/util/SeederApi/Controllers/InfoController.cs @@ -0,0 +1,20 @@ +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.SeederApi.Controllers; + +public class InfoController : Controller +{ + [HttpGet("~/alive")] + [HttpGet("~/now")] + public DateTime GetAlive() + { + return DateTime.UtcNow; + } + + [HttpGet("~/version")] + public JsonResult GetVersion() + { + return Json(AssemblyHelpers.GetVersion()); + } +} diff --git a/util/SeederApi/Controllers/QueryController.cs b/util/SeederApi/Controllers/QueryController.cs new file mode 100644 index 0000000000..22bf84e5b7 --- /dev/null +++ b/util/SeederApi/Controllers/QueryController.cs @@ -0,0 +1,32 @@ +using Bit.SeederApi.Execution; +using Bit.SeederApi.Models.Request; +using Bit.SeederApi.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.SeederApi.Controllers; + +[Route("query")] +public class QueryController(ILogger logger, IQueryExecutor queryExecutor) : Controller +{ + [HttpPost] + public IActionResult Query([FromBody] QueryRequestModel request) + { + logger.LogInformation("Executing query: {Query}", request.Template); + + try + { + var result = queryExecutor.Execute(request.Template, request.Arguments); + + return Json(result); + } + catch (QueryNotFoundException ex) + { + return NotFound(new { Error = ex.Message }); + } + catch (QueryExecutionException ex) + { + logger.LogError(ex, "Error executing query: {Query}", request.Template); + return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message }); + } + } +} diff --git a/util/SeederApi/Controllers/SeedController.cs b/util/SeederApi/Controllers/SeedController.cs new file mode 100644 index 0000000000..44f0dbaf2c --- /dev/null +++ b/util/SeederApi/Controllers/SeedController.cs @@ -0,0 +1,100 @@ +using Bit.SeederApi.Commands.Interfaces; +using Bit.SeederApi.Execution; +using Bit.SeederApi.Models.Request; +using Bit.SeederApi.Queries.Interfaces; +using Bit.SeederApi.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.SeederApi.Controllers; + +[Route("seed")] +public class SeedController( + ILogger logger, + ISceneExecutor sceneExecutor, + IDestroySceneCommand destroySceneCommand, + IDestroyBatchScenesCommand destroyBatchScenesCommand, + IGetAllPlayIdsQuery getAllPlayIdsQuery) : Controller +{ + [HttpPost] + public async Task SeedAsync([FromBody] SeedRequestModel request) + { + logger.LogInformation("Received seed request with template: {Template}", request.Template); + + try + { + var response = await sceneExecutor.ExecuteAsync(request.Template, request.Arguments); + + return Json(response); + } + catch (SceneNotFoundException ex) + { + return NotFound(new { Error = ex.Message }); + } + catch (SceneExecutionException ex) + { + logger.LogError(ex, "Error executing scene: {Template}", request.Template); + return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message }); + } + } + + [HttpDelete("batch")] + public async Task DeleteBatchAsync([FromBody] List playIds) + { + logger.LogInformation("Deleting batch of seeded data with IDs: {PlayIds}", string.Join(", ", playIds)); + + try + { + await destroyBatchScenesCommand.DestroyAsync(playIds); + return Ok(new { Message = "Batch delete completed successfully" }); + } + catch (AggregateException ex) + { + return BadRequest(new + { + Error = ex.Message, + Details = ex.InnerExceptions.Select(e => e.Message).ToList() + }); + } + } + + [HttpDelete("{playId}")] + public async Task DeleteAsync([FromRoute] string playId) + { + logger.LogInformation("Deleting seeded data with ID: {PlayId}", playId); + + try + { + var result = await destroySceneCommand.DestroyAsync(playId); + + return Json(result); + } + catch (SceneExecutionException ex) + { + logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId); + return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message }); + } + } + + + [HttpDelete] + public async Task DeleteAllAsync() + { + logger.LogInformation("Deleting all seeded data"); + + var playIds = getAllPlayIdsQuery.GetAllPlayIds(); + + try + { + await destroyBatchScenesCommand.DestroyAsync(playIds); + return NoContent(); + } + catch (AggregateException ex) + { + return BadRequest(new + { + Error = ex.Message, + Details = ex.InnerExceptions.Select(e => e.Message).ToList() + }); + } + } +} diff --git a/util/SeederApi/Execution/IQueryExecutor.cs b/util/SeederApi/Execution/IQueryExecutor.cs new file mode 100644 index 0000000000..ebd971bbb7 --- /dev/null +++ b/util/SeederApi/Execution/IQueryExecutor.cs @@ -0,0 +1,22 @@ +using System.Text.Json; + +namespace Bit.SeederApi.Execution; + +/// +/// Executor for dynamically resolving and executing queries by name. +/// This is an infrastructure component that orchestrates query execution, +/// not a domain-level query. +/// +public interface IQueryExecutor +{ + /// + /// Executes a query with the given query name and arguments. + /// Queries are read-only and do not track entities or create seed IDs. + /// + /// The name of the query (e.g., "EmergencyAccessInviteQuery") + /// Optional JSON arguments to pass to the query's Execute method + /// The result of the query execution + /// Thrown when the query is not found + /// Thrown when there's an error executing the query + object Execute(string queryName, JsonElement? arguments); +} diff --git a/util/SeederApi/Execution/ISceneExecutor.cs b/util/SeederApi/Execution/ISceneExecutor.cs new file mode 100644 index 0000000000..f15909ea79 --- /dev/null +++ b/util/SeederApi/Execution/ISceneExecutor.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using Bit.SeederApi.Models.Response; + +namespace Bit.SeederApi.Execution; + +/// +/// Executor for dynamically resolving and executing scenes by template name. +/// This is an infrastructure component that orchestrates scene execution, +/// not a domain-level command. +/// +public interface ISceneExecutor +{ + /// + /// Executes a scene with the given template name and arguments. + /// + /// The name of the scene template (e.g., "SingleUserScene") + /// Optional JSON arguments to pass to the scene's Seed method + /// A scene response model containing the result and mangle map + /// Thrown when the scene template is not found + /// Thrown when there's an error executing the scene + Task ExecuteAsync(string templateName, JsonElement? arguments); +} diff --git a/util/SeederApi/Execution/JsonConfiguration.cs b/util/SeederApi/Execution/JsonConfiguration.cs new file mode 100644 index 0000000000..beef36e62a --- /dev/null +++ b/util/SeederApi/Execution/JsonConfiguration.cs @@ -0,0 +1,19 @@ +using System.Text.Json; + +namespace Bit.SeederApi.Execution; + +/// +/// Provides shared JSON serialization configuration for executors. +/// +internal static class JsonConfiguration +{ + /// + /// Standard JSON serializer options used for deserializing scene and query request models. + /// Uses case-insensitive property matching and camelCase naming policy. + /// + internal static readonly JsonSerializerOptions Options = new() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; +} diff --git a/util/SeederApi/Execution/QueryExecutor.cs b/util/SeederApi/Execution/QueryExecutor.cs new file mode 100644 index 0000000000..5473586c22 --- /dev/null +++ b/util/SeederApi/Execution/QueryExecutor.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using Bit.Seeder; +using Bit.SeederApi.Services; + +namespace Bit.SeederApi.Execution; + +public class QueryExecutor( + ILogger logger, + IServiceProvider serviceProvider) : IQueryExecutor +{ + + public object Execute(string queryName, JsonElement? arguments) + { + try + { + var query = serviceProvider.GetKeyedService(queryName) + ?? throw new QueryNotFoundException(queryName); + + var requestType = query.GetRequestType(); + var requestModel = DeserializeRequestModel(queryName, requestType, arguments); + var result = query.Execute(requestModel); + + logger.LogInformation("Successfully executed query: {QueryName}", queryName); + return result; + } + catch (Exception ex) when (ex is not QueryNotFoundException and not QueryExecutionException) + { + logger.LogError(ex, "Unexpected error executing query: {QueryName}", queryName); + throw new QueryExecutionException( + $"An unexpected error occurred while executing query '{queryName}'", + ex.InnerException ?? ex); + } + } + + private object DeserializeRequestModel(string queryName, Type requestType, JsonElement? arguments) + { + if (arguments == null) + { + return CreateDefaultRequestModel(queryName, requestType); + } + + try + { + var requestModel = JsonSerializer.Deserialize(arguments.Value.GetRawText(), requestType, JsonConfiguration.Options); + if (requestModel == null) + { + throw new QueryExecutionException( + $"Failed to deserialize request model for query '{queryName}'"); + } + return requestModel; + } + catch (JsonException ex) + { + throw new QueryExecutionException( + $"Failed to deserialize request model for query '{queryName}': {ex.Message}", ex); + } + } + + private object CreateDefaultRequestModel(string queryName, Type requestType) + { + try + { + var requestModel = Activator.CreateInstance(requestType); + if (requestModel == null) + { + throw new QueryExecutionException( + $"Arguments are required for query '{queryName}'"); + } + return requestModel; + } + catch + { + throw new QueryExecutionException( + $"Arguments are required for query '{queryName}'"); + } + } +} diff --git a/util/SeederApi/Execution/SceneExecutor.cs b/util/SeederApi/Execution/SceneExecutor.cs new file mode 100644 index 0000000000..f31dd7d943 --- /dev/null +++ b/util/SeederApi/Execution/SceneExecutor.cs @@ -0,0 +1,78 @@ +using System.Text.Json; +using Bit.Seeder; +using Bit.SeederApi.Models.Response; +using Bit.SeederApi.Services; + +namespace Bit.SeederApi.Execution; + +public class SceneExecutor( + ILogger logger, + IServiceProvider serviceProvider) : ISceneExecutor +{ + + public async Task ExecuteAsync(string templateName, JsonElement? arguments) + { + try + { + var scene = serviceProvider.GetKeyedService(templateName) + ?? throw new SceneNotFoundException(templateName); + + var requestType = scene.GetRequestType(); + var requestModel = DeserializeRequestModel(templateName, requestType, arguments); + var result = await scene.SeedAsync(requestModel); + + logger.LogInformation("Successfully executed scene: {TemplateName}", templateName); + return SceneResponseModel.FromSceneResult(result); + } + catch (Exception ex) when (ex is not SceneNotFoundException and not SceneExecutionException) + { + logger.LogError(ex, "Unexpected error executing scene: {TemplateName}", templateName); + throw new SceneExecutionException( + $"An unexpected error occurred while executing scene '{templateName}'", + ex.InnerException ?? ex); + } + } + + private object DeserializeRequestModel(string templateName, Type requestType, JsonElement? arguments) + { + if (arguments == null) + { + return CreateDefaultRequestModel(templateName, requestType); + } + + try + { + var requestModel = JsonSerializer.Deserialize(arguments.Value.GetRawText(), requestType, JsonConfiguration.Options); + if (requestModel == null) + { + throw new SceneExecutionException( + $"Failed to deserialize request model for scene '{templateName}'"); + } + return requestModel; + } + catch (JsonException ex) + { + throw new SceneExecutionException( + $"Failed to deserialize request model for scene '{templateName}': {ex.Message}", ex); + } + } + + private object CreateDefaultRequestModel(string templateName, Type requestType) + { + try + { + var requestModel = Activator.CreateInstance(requestType); + if (requestModel == null) + { + throw new SceneExecutionException( + $"Arguments are required for scene '{templateName}'"); + } + return requestModel; + } + catch + { + throw new SceneExecutionException( + $"Arguments are required for scene '{templateName}'"); + } + } +} diff --git a/util/SeederApi/Extensions/ServiceCollectionExtensions.cs b/util/SeederApi/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..052da28dfc --- /dev/null +++ b/util/SeederApi/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,76 @@ +using System.Reflection; +using Bit.Seeder; +using Bit.SeederApi.Commands; +using Bit.SeederApi.Commands.Interfaces; +using Bit.SeederApi.Execution; +using Bit.SeederApi.Queries; +using Bit.SeederApi.Queries.Interfaces; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Bit.SeederApi.Extensions; + +public static class ServiceCollectionExtensions +{ + /// + /// Registers SeederApi executors, commands, and queries. + /// + public static IServiceCollection AddSeederApiServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + + return services; + } + + /// + /// Dynamically registers all scene types that implement IScene<TRequest> from the Seeder assembly. + /// Scenes are registered as keyed scoped services using their class name as the key. + /// + public static IServiceCollection AddScenes(this IServiceCollection services) + { + var iSceneType1 = typeof(IScene<>); + var iSceneType2 = typeof(IScene<,>); + var isIScene = (Type t) => t == iSceneType1 || t == iSceneType2; + + var seederAssembly = Assembly.Load("Seeder"); + var sceneTypes = seederAssembly.GetTypes() + .Where(t => t is { IsClass: true, IsAbstract: false } && + t.GetInterfaces().Any(i => i.IsGenericType && + isIScene(i.GetGenericTypeDefinition()))); + + foreach (var sceneType in sceneTypes) + { + services.TryAddScoped(sceneType); + services.TryAddKeyedScoped(typeof(IScene), sceneType.Name, (sp, _) => sp.GetRequiredService(sceneType)); + } + + return services; + } + + /// + /// Dynamically registers all query types that implement IQuery<TRequest> from the Seeder assembly. + /// Queries are registered as keyed scoped services using their class name as the key. + /// + public static IServiceCollection AddQueries(this IServiceCollection services) + { + var iQueryType = typeof(IQuery<,>); + var seederAssembly = Assembly.Load("Seeder"); + var queryTypes = seederAssembly.GetTypes() + .Where(t => t is { IsClass: true, IsAbstract: false } && + t.GetInterfaces().Any(i => i.IsGenericType && + i.GetGenericTypeDefinition() == iQueryType)); + + foreach (var queryType in queryTypes) + { + services.TryAddScoped(queryType); + services.TryAddKeyedScoped(typeof(IQuery), queryType.Name, (sp, _) => sp.GetRequiredService(queryType)); + } + + return services; + } +} diff --git a/util/SeederApi/Models/Request/QueryRequestModel.cs b/util/SeederApi/Models/Request/QueryRequestModel.cs new file mode 100644 index 0000000000..38751bc21b --- /dev/null +++ b/util/SeederApi/Models/Request/QueryRequestModel.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +namespace Bit.SeederApi.Models.Request; + +public class QueryRequestModel +{ + [Required] + public required string Template { get; set; } + public JsonElement? Arguments { get; set; } +} diff --git a/util/SeederApi/Models/Request/SeedRequestModel.cs b/util/SeederApi/Models/Request/SeedRequestModel.cs new file mode 100644 index 0000000000..404af97ebe --- /dev/null +++ b/util/SeederApi/Models/Request/SeedRequestModel.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +namespace Bit.SeederApi.Models.Request; + +public class SeedRequestModel +{ + [Required] + public required string Template { get; set; } + public JsonElement? Arguments { get; set; } +} diff --git a/util/SeederApi/Models/Response/SeedResponseModel.cs b/util/SeederApi/Models/Response/SeedResponseModel.cs new file mode 100644 index 0000000000..a8913c76c8 --- /dev/null +++ b/util/SeederApi/Models/Response/SeedResponseModel.cs @@ -0,0 +1,18 @@ +using Bit.Seeder; + +namespace Bit.SeederApi.Models.Response; + +public class SceneResponseModel +{ + public required Dictionary? MangleMap { get; init; } + public required object? Result { get; init; } + + public static SceneResponseModel FromSceneResult(SceneResult sceneResult) + { + return new SceneResponseModel + { + Result = sceneResult.Result, + MangleMap = sceneResult.MangleMap, + }; + } +} diff --git a/util/SeederApi/Program.cs b/util/SeederApi/Program.cs new file mode 100644 index 0000000000..2067df307a --- /dev/null +++ b/util/SeederApi/Program.cs @@ -0,0 +1,20 @@ +using Bit.Core.Utilities; + +namespace Bit.SeederApi; + +public class Program +{ + public static void Main(string[] args) + { + Host + .CreateDefaultBuilder(args) + .ConfigureCustomAppConfiguration(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .AddSerilogFileLogging() + .Build() + .Run(); + } +} diff --git a/util/SeederApi/Properties/launchSettings.json b/util/SeederApi/Properties/launchSettings.json new file mode 100644 index 0000000000..95cd77e255 --- /dev/null +++ b/util/SeederApi/Properties/launchSettings.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5047", + "sslPort": 0 + } + }, + "profiles": { + "SeederApi": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5047", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SeederApi-SelfHost": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5048", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "developSelfHosted": "true" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/util/SeederApi/Queries/GetAllPlayIdsQuery.cs b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs new file mode 100644 index 0000000000..7bc72e5b07 --- /dev/null +++ b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs @@ -0,0 +1,15 @@ +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.SeederApi.Queries.Interfaces; + +namespace Bit.SeederApi.Queries; + +public class GetAllPlayIdsQuery(DatabaseContext databaseContext) : IGetAllPlayIdsQuery +{ + public List GetAllPlayIds() + { + return databaseContext.PlayItem + .Select(pd => pd.PlayId) + .Distinct() + .ToList(); + } +} diff --git a/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs b/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs new file mode 100644 index 0000000000..ea9c44991a --- /dev/null +++ b/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs @@ -0,0 +1,13 @@ +namespace Bit.SeederApi.Queries.Interfaces; + +/// +/// Query for retrieving all play IDs for currently tracked seeded data. +/// +public interface IGetAllPlayIdsQuery +{ + /// + /// Retrieves all play IDs for currently tracked seeded data. + /// + /// A list of play IDs representing active seeded data that can be destroyed. + List GetAllPlayIds(); +} diff --git a/util/SeederApi/README.md b/util/SeederApi/README.md new file mode 100644 index 0000000000..a5a5d4ab9f --- /dev/null +++ b/util/SeederApi/README.md @@ -0,0 +1,185 @@ +# SeederApi + +A web API for dynamically seeding and querying test data in the Bitwarden database during development and testing. + +## Overview + +The SeederApi provides HTTP endpoints to execute [Seeder](../Seeder/README.md) scenes and queries, enabling automated test data +generation and retrieval through a RESTful interface. This is particularly useful for integration testing, local +development workflows, and automated test environments. + +## Architecture + +The SeederApi consists of three main components: + +1. **Controllers** - HTTP endpoints for seeding, querying, and managing test data +2. **Services** - Business logic for scene and query execution +3. **Models** - Request/response models for API communication + +### Key Components + +- **SeedController** (`/seed`) - Creates and destroys seeded test data +- **QueryController** (`/query`) - Executes read-only queries against existing data +- **InfoController** (`/alive`, `/version`) - Health check and version information +- **SceneService** - Manages scene execution and cleanup with play ID tracking +- **QueryService** - Executes read-only query operations + +## How To Use + +### Starting the API + +```bash +cd util/SeederApi +dotnet run +``` + +The API will start on the configured port (typically `http://localhost:5000`). + +### Seeding Data + +Send a POST request to `/seed` with a scene template name and optional arguments. Include the `X-Play-Id` header to +track the seeded data for later cleanup: + +```bash +curl -X POST http://localhost:5000/seed \ + -H "Content-Type: application/json" \ + -H "X-Play-Id: test-run-123" \ + -d '{ + "template": "SingleUserScene", + "arguments": { + "email": "test@example.com" + } + }' +``` + +**Response:** + +```json +{ + "mangleMap": { + "test@example.com": "1854b016+test@example.com", + "42bcf05d-7ad0-4e27-8b53-b3b700acc664": "42bcf05d-7ad0-4e27-8b53-b3b700acc664" + }, + "result": null +} +``` + +The `result` contains the data returned by the scene, and `mangleMap` contains ID mappings if ID mangling is enabled. +Use the `X-Play-Id` header value to later destroy the seeded data. + +### Querying Data + +Send a POST request to `/query` to execute read-only queries: + +```bash +curl -X POST http://localhost:5000/query \ + -H "Content-Type: application/json" \ + -d '{ + "template": "EmergencyAccessInviteQuery", + "arguments": { + "email": "test@example.com" + } + }' +``` + +**Response:** + +```json +["/accept-emergency?..."] +``` + +### Destroying Seeded Data + +#### Delete by Play ID + +Use the same play ID value you provided in the `X-Play-Id` header: + +```bash +curl -X DELETE http://localhost:5000/seed/test-run-123 +``` + +#### Delete Multiple by Play IDs + +```bash +curl -X DELETE http://localhost:5000/seed/batch \ + -H "Content-Type: application/json" \ + -d '["test-run-123", "test-run-456"]' +``` + +#### Delete All Seeded Data + +```bash +curl -X DELETE http://localhost:5000/seed +``` + +### Health Checks + +```bash +# Check if API is alive +curl http://localhost:5000/alive + +# Get API version +curl http://localhost:5000/version +``` + +## Creating Scenes and Queries + +Scenes and queries are defined in the [Seeder](../Seeder/README.md) project. The SeederApi automatically discovers and registers all +classes implementing the scene and query interfaces. + +## Configuration + +The SeederApi uses the standard Bitwarden configuration system: + +- `appsettings.json` - Base configuration +- `appsettings.Development.json` - Development overrides +- `dev/secrets.json` - Local secrets (database connection strings, etc.) +- User Secrets ID: `bitwarden-seeder-api` + +### Required Settings + +The SeederApi requires the following configuration: + +- **Database Connection** - Connection string to the Bitwarden database +- **Global Settings** - Standard Bitwarden `GlobalSettings` configuration + +## Play ID Tracking + +Certain entities such as Users and Organizations are tracked when created by a request including a PlayId. This enables +entities to be deleted after using the PlayId. + +### The X-Play-Id Header + +**Important:** All seed requests should include the `X-Play-Id` header: + +```bash +-H "X-Play-Id: your-unique-identifier" +``` + +The play ID can be any string that uniquely identifies your test run or session. Common patterns: + +### How Play ID Tracking Works + +When `TestPlayIdTrackingEnabled` is enabled in GlobalSettings, the `PlayIdMiddleware` +(see `src/SharedWeb/Utilities/PlayIdMiddleware.cs:7-23`) automatically: + +1. **Extracts** the `X-Play-Id` header from incoming requests +2. **Sets** the play ID in the `PlayIdService` for the request scope +3. **Tracks** all entities (users, organizations, etc.) created during the request +4. **Associates** them with the play ID in the `PlayItem` table +5. **Enables** complete cleanup via the delete endpoints + +This tracking works for **any API request** that includes the `X-Play-Id` header, not just SeederApi endpoints. This means +you can track entities created through: + +- **Scene executions** - Data seeded via `/seed` endpoint +- **Regular API operations** - Users signing up, creating organizations, inviting members, etc. +- **Integration tests** - Any HTTP requests to the Bitwarden API during test execution + +Without the `X-Play-Id` header, entities will not be tracked and cannot be cleaned up using the delete endpoints. + +## Security Considerations + +> [!WARNING] +> The SeederApi is intended for **development and testing environments only**. Never deploy this API to production +> environments. diff --git a/util/SeederApi/SeederApi.csproj b/util/SeederApi/SeederApi.csproj new file mode 100644 index 0000000000..53e9941c1c --- /dev/null +++ b/util/SeederApi/SeederApi.csproj @@ -0,0 +1,16 @@ + + + + bitwarden-seeder-api + net8.0 + enable + enable + false + + + + + + + + diff --git a/util/SeederApi/Services/QueryExceptions.cs b/util/SeederApi/Services/QueryExceptions.cs new file mode 100644 index 0000000000..beb0625cbb --- /dev/null +++ b/util/SeederApi/Services/QueryExceptions.cs @@ -0,0 +1,10 @@ +namespace Bit.SeederApi.Services; + +public class QueryNotFoundException(string query) : Exception($"Query '{query}' not found"); + +public class QueryExecutionException : Exception +{ + public QueryExecutionException(string message) : base(message) { } + public QueryExecutionException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/util/SeederApi/Services/SceneExceptions.cs b/util/SeederApi/Services/SceneExceptions.cs new file mode 100644 index 0000000000..2d8da19629 --- /dev/null +++ b/util/SeederApi/Services/SceneExceptions.cs @@ -0,0 +1,10 @@ +namespace Bit.SeederApi.Services; + +public class SceneNotFoundException(string scene) : Exception($"Scene '{scene}' not found"); + +public class SceneExecutionException : Exception +{ + public SceneExecutionException(string message) : base(message) { } + public SceneExecutionException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/util/SeederApi/Startup.cs b/util/SeederApi/Startup.cs new file mode 100644 index 0000000000..420078f509 --- /dev/null +++ b/util/SeederApi/Startup.cs @@ -0,0 +1,80 @@ +using System.Globalization; +using Bit.Core.Settings; +using Bit.Seeder; +using Bit.Seeder.Factories; +using Bit.SeederApi.Extensions; +using Bit.SharedWeb.Utilities; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Bit.SeederApi; + +public class Startup +{ + public Startup(IWebHostEnvironment env, IConfiguration configuration) + { + CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); + Configuration = configuration; + Environment = env; + } + + public IConfiguration Configuration { get; private set; } + public IWebHostEnvironment Environment { get; set; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddOptions(); + + var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment); + + services.AddCustomDataProtectionServices(Environment, globalSettings); + + services.AddTokenizers(); + services.AddDatabaseRepositories(globalSettings); + services.AddTestPlayIdTracking(globalSettings); + + services.TryAddSingleton(); + + services.AddScoped, PasswordHasher>(); + + services.AddSingleton(); + services.AddScoped(); + + services.AddSeederApiServices(); + + services.AddScoped(_ => new MangleId()); + services.AddScenes(); + services.AddQueries(); + + services.AddControllers(); + } + + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IHostApplicationLifetime appLifetime, + GlobalSettings globalSettings) + { + if (env.IsProduction()) + { + throw new InvalidOperationException( + "SeederApi cannot be run in production environments. This service is intended for test data generation only."); + } + + if (globalSettings.TestPlayIdTrackingEnabled) + { + app.UseMiddleware(); + } + + if (!env.IsDevelopment()) + { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}"); + }); + } +} diff --git a/util/SeederApi/appsettings.Development.json b/util/SeederApi/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/util/SeederApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/util/SeederApi/appsettings.json b/util/SeederApi/appsettings.json new file mode 100644 index 0000000000..79388a1bb0 --- /dev/null +++ b/util/SeederApi/appsettings.json @@ -0,0 +1,11 @@ +{ + "globalSettings": { + "projectName": "SeederApi" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.Designer.cs b/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.Designer.cs new file mode 100644 index 0000000000..eac0b557a4 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.Designer.cs @@ -0,0 +1,3491 @@ +// +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("20260108193841_CreatePlayItem")] + partial class CreatePlayItem + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("SyncSeats") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseDisableSmAdsForUsers") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePhishingBlocker") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp", "UsersGetPremium" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordCount") + .HasColumnType("INTEGER"); + + b.Property("MemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("MemberCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("PasswordCount") + .HasColumnType("INTEGER"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("AuthType") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .HasColumnType("INTEGER"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("EditorServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VersionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Archives") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.cs b/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.cs new file mode 100644 index 0000000000..ae62e38f23 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20260108193841_CreatePlayItem.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class CreatePlayItem : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PlayItem", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + PlayId = table.Column(type: "TEXT", maxLength: 256, nullable: false), + UserId = table.Column(type: "TEXT", nullable: true), + OrganizationId = table.Column(type: "TEXT", nullable: true), + CreationDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayItem", x => x.Id); + table.CheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + table.ForeignKey( + name: "FK_PlayItem_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PlayItem_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_OrganizationId", + table: "PlayItem", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_PlayId", + table: "PlayItem", + column: "PlayId"); + + migrationBuilder.CreateIndex( + name: "IX_PlayItem_UserId", + table: "PlayItem", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PlayItem"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index a30b959ce9..4605ae8939 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -277,71 +277,6 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("Organization", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Configuration") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("OrganizationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId") - .HasAnnotation("SqlServer:Clustered", false); - - b.HasIndex("OrganizationId", "Type") - .IsUnique() - .HasAnnotation("SqlServer:Clustered", false); - - b.ToTable("OrganizationIntegration", (string)null); - }); - - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("Configuration") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("EventType") - .HasColumnType("INTEGER"); - - b.Property("Filters") - .HasColumnType("TEXT"); - - b.Property("OrganizationIntegrationId") - .HasColumnType("TEXT"); - - b.Property("RevisionDate") - .HasColumnType("TEXT"); - - b.Property("Template") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationIntegrationId"); - - b.ToTable("OrganizationIntegrationConfiguration", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => { b.Property("Id") @@ -621,7 +556,7 @@ namespace Bit.SqliteMigrations.Migrations b.Property("Type") .HasColumnType("INTEGER"); - b.Property("WaitTimeDays") + b.Property("WaitTimeDays") .HasColumnType("INTEGER"); b.HasKey("Id"); @@ -1004,6 +939,71 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("OrganizationApplication", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.Property("Id") @@ -1616,6 +1616,42 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("OrganizationUser", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlayId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("PlayId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PlayItem", null, t => + { + t.HasCheckConstraint("CK_PlayItem_UserOrOrganization", "(\"UserId\" IS NOT NULL AND \"OrganizationId\" IS NULL) OR (\"UserId\" IS NULL AND \"OrganizationId\" IS NOT NULL)"); + }); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.Property("Id") @@ -2596,28 +2632,6 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -2796,6 +2810,28 @@ namespace Bit.SqliteMigrations.Migrations b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -2992,6 +3028,23 @@ namespace Bit.SqliteMigrations.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.PlayItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") From d1fdaa6a2f4620a876e5166c23e6e41bbb819021 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 14 Jan 2026 15:02:49 +0100 Subject: [PATCH 47/58] Fix lint on main (#6835) --- util/Seeder/Factories/UserSeeder.cs | 5 +++-- util/Seeder/MangleId.cs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/util/Seeder/Factories/UserSeeder.cs b/util/Seeder/Factories/UserSeeder.cs index dd5fc159c0..4fc456981c 100644 --- a/util/Seeder/Factories/UserSeeder.cs +++ b/util/Seeder/Factories/UserSeeder.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using System.Globalization; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Utilities; using Bit.RustSDK; @@ -77,7 +78,7 @@ public class UserSeeder(RustSdkService sdkService, IPasswordHasher /// Helper for generating unique identifier suffixes to prevent collisions in test data. @@ -12,7 +14,7 @@ public class MangleId public MangleId() { // Generate a short random string (6 char) to use as the mangle ID - Value = Random.Shared.NextInt64().ToString("x").Substring(0, 8); + Value = Random.Shared.NextInt64().ToString("x", CultureInfo.InvariantCulture).Substring(0, 8); } public override string ToString() => Value; From 02cec2d9fea5651b76169343d3212b49d41b1fe3 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Wed, 14 Jan 2026 10:42:06 -0600 Subject: [PATCH 48/58] Removing unused feature flag. (#6836) --- src/Core/Constants.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 47e7eb40bd..c42db8bbef 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -142,7 +142,6 @@ public static class FeatureFlagKeys public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache"; public const string BlockClaimedDomainAccountCreation = "pm-28297-block-uninvited-claimed-domain-registration"; public const string IncreaseBulkReinviteLimitForCloud = "pm-28251-increase-bulk-reinvite-limit-for-cloud"; - public const string BulkRevokeUsersV2 = "pm-28456-bulk-revoke-users-v2"; public const string PremiumAccessQuery = "pm-21411-premium-access-query"; /* Architecture */ From 2224b1e0c697311c49f66382828bb47003a5b9de Mon Sep 17 00:00:00 2001 From: Github Actions Date: Wed, 14 Jan 2026 17:05:02 +0000 Subject: [PATCH 49/58] Bumped version to 2026.1.0 --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9438ef3351..e7a8422605 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net8.0 - 2025.12.2 + 2026.1.0 Bit.$(MSBuildProjectName) enable @@ -30,4 +30,4 @@ 4.18.1 - + \ No newline at end of file From aa8d7c6775fb333420eb8df18bf5305ce6f1b89a Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Wed, 14 Jan 2026 12:19:23 -0500 Subject: [PATCH 50/58] [PM-30682] Add missing null check, update tests (#6826) * add missing null check, update tests * CR feedback --- ...maticUserConfirmationPolicyEventHandler.cs | 8 ++- ...UserConfirmationPolicyEventHandlerTests.cs | 52 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs index 86c94147f4..213d18c27d 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs @@ -74,8 +74,12 @@ public class AutomaticUserConfirmationPolicyEventHandler( private async Task ValidateUserComplianceWithSingleOrgAsync(Guid organizationId, ICollection organizationUsers) { - var hasNonCompliantUser = (await organizationUserRepository.GetManyByManyUsersAsync( - organizationUsers.Select(ou => ou.UserId!.Value))) + var userIds = organizationUsers.Where( + u => u.UserId is not null && + u.Status != OrganizationUserStatusType.Invited) + .Select(u => u.UserId!.Value); + + var hasNonCompliantUser = (await organizationUserRepository.GetManyByManyUsersAsync(userIds)) .Any(uo => uo.OrganizationId != organizationId && uo.Status != OrganizationUserStatusType.Invited); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs index 3c9fd9a9e9..e2c9de4d6f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs @@ -283,7 +283,7 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests OrganizationId = policyUpdate.OrganizationId, Type = OrganizationUserType.User, Status = OrganizationUserStatusType.Invited, - UserId = Guid.NewGuid(), + UserId = null, Email = "invited@example.com" }; @@ -302,6 +302,56 @@ public class AutomaticUserConfirmationPolicyEventHandlerTests Assert.True(string.IsNullOrEmpty(result)); } + [Theory, BitAutoData] + public async Task ValidateAsync_EnablingPolicy_MixedUsersWithNullUserId_HandlesCorrectly( + [PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate, + Guid confirmedUserId, + SutProvider sutProvider) + { + // Arrange + var invitedUser = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + OrganizationId = policyUpdate.OrganizationId, + Type = OrganizationUserType.User, + Status = OrganizationUserStatusType.Invited, + UserId = null, + Email = "invited@example.com" + }; + + var confirmedUser = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + OrganizationId = policyUpdate.OrganizationId, + Type = OrganizationUserType.User, + Status = OrganizationUserStatusType.Confirmed, + UserId = confirmedUserId, + Email = "confirmed@example.com" + }; + + sutProvider.GetDependency() + .GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId) + .Returns([invitedUser, confirmedUser]); + + sutProvider.GetDependency() + .GetManyByManyUsersAsync(Arg.Any>()) + .Returns([]); + + sutProvider.GetDependency() + .GetManyByManyUsersAsync(Arg.Any>()) + .Returns([]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + + // Assert + Assert.True(string.IsNullOrEmpty(result)); + + await sutProvider.GetDependency() + .Received(1) + .GetManyByManyUsersAsync(Arg.Is>(ids => ids.Count() == 1 && ids.First() == confirmedUserId)); + } + [Theory, BitAutoData] public async Task ValidateAsync_EnablingPolicy_RevokedUsersIncluded_InComplianceCheck( [PolicyUpdate(PolicyType.AutomaticUserConfirmation)] PolicyUpdate policyUpdate, From 2a18f276a818bec12bc5a938be723250872bb8a4 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:21:49 -0600 Subject: [PATCH 51/58] fix: feature flag key for PremiumAccessQuery, refs PM-21411 (#6834) --- src/Core/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index c42db8bbef..723707424d 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -142,7 +142,7 @@ public static class FeatureFlagKeys public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache"; public const string BlockClaimedDomainAccountCreation = "pm-28297-block-uninvited-claimed-domain-registration"; public const string IncreaseBulkReinviteLimitForCloud = "pm-28251-increase-bulk-reinvite-limit-for-cloud"; - public const string PremiumAccessQuery = "pm-21411-premium-access-query"; + public const string PremiumAccessQuery = "pm-29495-refactor-premium-interface"; /* Architecture */ public const string DesktopMigrationMilestone1 = "desktop-ui-migration-milestone-1"; From 4bff67ea1231ffe1f2e3e96c16c13818e09ad16e Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Wed, 14 Jan 2026 15:32:58 -0500 Subject: [PATCH 52/58] [PM-30687] Remove Desktop Send UI refresh feature flag (#6842) --- src/Core/Constants.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 723707424d..cc92397269 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -229,7 +229,6 @@ public static class FeatureFlagKeys /// Enable this flag to share the send view used by the web and browser clients /// on the desktop client. /// - public const string DesktopSendUIRefresh = "desktop-send-ui-refresh"; public const string UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators"; public const string UseChromiumImporter = "pm-23982-chromium-importer"; public const string ChromiumImporterWithABE = "pm-25855-chromium-importer-abe"; From e1b6e496f9f715325a144e32589674b680f249d2 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:34:42 -0600 Subject: [PATCH 53/58] fix(start-premium): Need to expand 'customer' on first invoice (#6844) --- ...CreatePremiumCloudHostedSubscriptionCommand.cs | 3 ++- ...ePremiumCloudHostedSubscriptionCommandTests.cs | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs index d52c79c1ee..764406ee56 100644 --- a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs +++ b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs @@ -361,7 +361,8 @@ public class CreatePremiumCloudHostedSubscriptionCommand( var invoice = await stripeAdapter.UpdateInvoiceAsync(subscription.LatestInvoiceId, new InvoiceUpdateOptions { - AutoAdvance = false + AutoAdvance = false, + Expand = ["customer"] }); await braintreeService.PayInvoice(new UserId(userId), invoice); diff --git a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs index 55eb69cc64..da287dc02b 100644 --- a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs +++ b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs @@ -266,7 +266,10 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests await _stripeAdapter.Received(1).CreateSubscriptionAsync(Arg.Any()); await _subscriberService.Received(1).CreateBraintreeCustomer(user, paymentMethod.Token); await _stripeAdapter.Received(1).UpdateInvoiceAsync(mockSubscription.LatestInvoiceId, - Arg.Is(opts => opts.AutoAdvance == false)); + Arg.Is(opts => + opts.AutoAdvance == false && + opts.Expand != null && + opts.Expand.Contains("customer"))); await _braintreeService.Received(1).PayInvoice(Arg.Any(), mockInvoice); await _userService.Received(1).SaveUserAsync(user); await _pushNotificationService.Received(1).PushSyncVaultAsync(user.Id); @@ -502,7 +505,10 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests Assert.True(user.Premium); Assert.Equal(mockSubscription.GetCurrentPeriodEnd(), user.PremiumExpirationDate); await _stripeAdapter.Received(1).UpdateInvoiceAsync(mockSubscription.LatestInvoiceId, - Arg.Is(opts => opts.AutoAdvance == false)); + Arg.Is(opts => + opts.AutoAdvance == false && + opts.Expand != null && + opts.Expand.Contains("customer"))); await _braintreeService.Received(1).PayInvoice(Arg.Any(), mockInvoice); } @@ -612,7 +618,10 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests Assert.False(user.Premium); Assert.Null(user.PremiumExpirationDate); await _stripeAdapter.Received(1).UpdateInvoiceAsync(mockSubscription.LatestInvoiceId, - Arg.Is(opts => opts.AutoAdvance == false)); + Arg.Is(opts => + opts.AutoAdvance == false && + opts.Expand != null && + opts.Expand.Contains("customer"))); await _braintreeService.Received(1).PayInvoice(Arg.Any(), mockInvoice); } From fa845a4753f1100468d25bc9dd24173b778c7636 Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:07:46 -0700 Subject: [PATCH 54/58] [Tools] Update SendAuthenticationQuery, add new non-anonymous endpoints, and add PutRemoveAuth endpoint (#6786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update send api models to support new `email` field * normalize authentication field evaluation order * document send response converters * add FIXME to remove unused constructor argument * add FIXME to remove unused constructor argument * introduce `tools-send-email-otp-listing` feature flag * add `ISendOwnerQuery` to dependency graph * fix broken tests * added AuthType prop to send related models with test coverage and debt cleanup * dotnet format * add migrations * dotnet format * make SendsController null safe (tech debt) * add AuthType col to Sends table, change Emails col length to 4000, and run migrations * dotnet format * update SPs to expect AuthType * include SP updates in migrations * remove migrations not intended for merge * Revert "remove migrations not intended for merge" This reverts commit 7df56e346ab3402387bebffc1d112d73214632fa. undo migrations removal * extract AuthType inference to util method and remove SQLite file * fix lints * address review comments * fix incorrect assignment and adopt SQL conventions * fix column assignment order in Send_Update.sql * remove space added to email list * assign SQL default value of NULL to AuthType * update SPs to match migration changes * remove FF, update SendAuthQuery, and update tests * new endpoints added but lack test coverage * dotnet format * add PutRemoveAuth endpoint with test coverage and tests for new non-anon endpoints * update RequireFeatureFlag comment for clarity * respond to Claude's findings * add additional validation logic to new auth endpoints * enforce auth policies on individual action methods * remove JsonConverter directive for AuthType * remove tools-send-email-otp-listing feature flag --------- Co-authored-by: ✨ Audrey ✨ Co-authored-by: ✨ Audrey ✨ Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> --- src/Api/Tools/Controllers/SendsController.cs | 142 +++- src/Core/Constants.cs | 10 - src/Core/Tools/Enums/AuthType.cs | 5 +- .../Queries/SendAuthenticationQuery.cs | 7 +- .../SendFeatures/Queries/SendOwnerQuery.cs | 14 +- .../Tools/Controllers/SendsControllerTests.cs | 704 +++++++++++++++++- .../Services/SendAuthenticationQueryTests.cs | 22 +- .../Tools/Services/SendOwnerQueryTests.cs | 30 +- 8 files changed, 842 insertions(+), 92 deletions(-) diff --git a/src/Api/Tools/Controllers/SendsController.cs b/src/Api/Tools/Controllers/SendsController.cs index 449d9573fd..f9f71d076d 100644 --- a/src/Api/Tools/Controllers/SendsController.cs +++ b/src/Api/Tools/Controllers/SendsController.cs @@ -5,9 +5,11 @@ using Bit.Api.Tools.Models.Request; using Bit.Api.Tools.Models.Response; using Bit.Api.Utilities; using Bit.Core; +using Bit.Core.Auth.Identity; +using Bit.Core.Auth.UserFeatures.SendAccess; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Services; -using Bit.Core.Settings; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Data; using Bit.Core.Tools.Repositories; @@ -22,7 +24,6 @@ using Microsoft.AspNetCore.Mvc; namespace Bit.Api.Tools.Controllers; [Route("sends")] -[Authorize("Application")] public class SendsController : Controller { private readonly ISendRepository _sendRepository; @@ -31,11 +32,10 @@ public class SendsController : Controller private readonly ISendFileStorageService _sendFileStorageService; private readonly IAnonymousSendCommand _anonymousSendCommand; private readonly INonAnonymousSendCommand _nonAnonymousSendCommand; - private readonly ISendOwnerQuery _sendOwnerQuery; - private readonly ILogger _logger; - private readonly GlobalSettings _globalSettings; + private readonly IFeatureService _featureService; + private readonly IPushNotificationService _pushNotificationService; public SendsController( ISendRepository sendRepository, @@ -46,7 +46,8 @@ public class SendsController : Controller ISendOwnerQuery sendOwnerQuery, ISendFileStorageService sendFileStorageService, ILogger logger, - GlobalSettings globalSettings) + IFeatureService featureService, + IPushNotificationService pushNotificationService) { _sendRepository = sendRepository; _userService = userService; @@ -56,10 +57,12 @@ public class SendsController : Controller _sendOwnerQuery = sendOwnerQuery; _sendFileStorageService = sendFileStorageService; _logger = logger; - _globalSettings = globalSettings; + _featureService = featureService; + _pushNotificationService = pushNotificationService; } #region Anonymous endpoints + [AllowAnonymous] [HttpPost("access/{id}")] public async Task Access(string id, [FromBody] SendAccessRequestModel model) @@ -73,21 +76,32 @@ public class SendsController : Controller var guid = new Guid(CoreHelpers.Base64UrlDecode(id)); var send = await _sendRepository.GetByIdAsync(guid); + if (send == null) { throw new BadRequestException("Could not locate send"); } + + /* This guard can be removed once feature flag is retired*/ + var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); + if (sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + { + return new UnauthorizedResult(); + } + var sendAuthResult = await _sendAuthorizationService.AccessAsync(send, model.Password); if (sendAuthResult.Equals(SendAccessResult.PasswordRequired)) { return new UnauthorizedResult(); } + if (sendAuthResult.Equals(SendAccessResult.PasswordInvalid)) { await Task.Delay(2000); throw new BadRequestException("Invalid password."); } + if (sendAuthResult.Equals(SendAccessResult.Denied)) { throw new NotFoundException(); @@ -99,6 +113,7 @@ public class SendsController : Controller var creator = await _userService.GetUserByIdAsync(send.UserId.Value); sendResponse.CreatorIdentifier = creator.Email; } + return new ObjectResult(sendResponse); } @@ -122,6 +137,13 @@ public class SendsController : Controller throw new BadRequestException("Could not locate send"); } + /* This guard can be removed once feature flag is retired*/ + var sendEmailOtpEnabled = _featureService.IsEnabled(FeatureFlagKeys.SendEmailOTP); + if (sendEmailOtpEnabled && send.AuthType == AuthType.Email && send.Emails is not null) + { + return new UnauthorizedResult(); + } + var (url, result) = await _anonymousSendCommand.GetSendFileDownloadUrlAsync(send, fileId, model.Password); @@ -129,21 +151,19 @@ public class SendsController : Controller { return new UnauthorizedResult(); } + if (result.Equals(SendAccessResult.PasswordInvalid)) { await Task.Delay(2000); throw new BadRequestException("Invalid password."); } + if (result.Equals(SendAccessResult.Denied)) { throw new NotFoundException(); } - return new ObjectResult(new SendFileDownloadDataResponseModel() - { - Id = fileId, - Url = url, - }); + return new ObjectResult(new SendFileDownloadDataResponseModel() { Id = fileId, Url = url, }); } [AllowAnonymous] @@ -157,7 +177,8 @@ public class SendsController : Controller { try { - var blobName = eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1]; + var blobName = + eventGridEvent.Subject.Split($"{AzureSendFileStorageService.FilesContainerName}/blobs/")[1]; var sendId = AzureSendFileStorageService.SendIdFromBlobName(blobName); var send = await _sendRepository.GetByIdAsync(new Guid(sendId)); if (send == null) @@ -166,6 +187,7 @@ public class SendsController : Controller { await azureSendFileStorageService.DeleteBlobAsync(blobName); } + return; } @@ -173,7 +195,8 @@ public class SendsController : Controller } catch (Exception e) { - _logger.LogError(e, "Uncaught exception occurred while handling event grid event: {Event}", JsonSerializer.Serialize(eventGridEvent)); + _logger.LogError(e, "Uncaught exception occurred while handling event grid event: {Event}", + JsonSerializer.Serialize(eventGridEvent)); return; } } @@ -185,6 +208,7 @@ public class SendsController : Controller #region Non-anonymous endpoints + [Authorize(Policies.Application)] [HttpGet("{id}")] public async Task Get(string id) { @@ -193,6 +217,7 @@ public class SendsController : Controller return new SendResponseModel(send); } + [Authorize(Policies.Application)] [HttpGet("")] public async Task> GetAll() { @@ -203,6 +228,67 @@ public class SendsController : Controller return result; } + [Authorize(Policy = Policies.Send)] + // [RequireFeature(FeatureFlagKeys.SendEmailOTP)] /* Uncomment once client fallback re-try logic is added */ + [HttpPost("access/")] + public async Task AccessUsingAuth() + { + var guid = User.GetSendId(); + var send = await _sendRepository.GetByIdAsync(guid); + if (send == null) + { + throw new BadRequestException("Could not locate send"); + } + if (send.MaxAccessCount.GetValueOrDefault(int.MaxValue) <= send.AccessCount || + send.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow || send.Disabled || + send.DeletionDate < DateTime.UtcNow) + { + throw new NotFoundException(); + } + + var sendResponse = new SendAccessResponseModel(send); + if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault()) + { + var creator = await _userService.GetUserByIdAsync(send.UserId.Value); + sendResponse.CreatorIdentifier = creator.Email; + } + + send.AccessCount++; + await _sendRepository.ReplaceAsync(send); + await _pushNotificationService.PushSyncSendUpdateAsync(send); + + return new ObjectResult(sendResponse); + } + + [Authorize(Policy = Policies.Send)] + // [RequireFeature(FeatureFlagKeys.SendEmailOTP)] /* Uncomment once client fallback re-try logic is added */ + [HttpPost("access/file/{fileId}")] + public async Task GetSendFileDownloadDataUsingAuth(string fileId) + { + var sendId = User.GetSendId(); + var send = await _sendRepository.GetByIdAsync(sendId); + + if (send == null) + { + throw new BadRequestException("Could not locate send"); + } + if (send.MaxAccessCount.GetValueOrDefault(int.MaxValue) <= send.AccessCount || + send.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow || send.Disabled || + send.DeletionDate < DateTime.UtcNow) + { + throw new NotFoundException(); + } + + var url = await _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId); + + send.AccessCount++; + await _sendRepository.ReplaceAsync(send); + await _pushNotificationService.PushSyncSendUpdateAsync(send); + + return new ObjectResult(new SendFileDownloadDataResponseModel() { Id = fileId, Url = url }); + } + + [Authorize(Policies.Application)] [HttpPost("")] public async Task Post([FromBody] SendRequestModel model) { @@ -213,6 +299,7 @@ public class SendsController : Controller return new SendResponseModel(send); } + [Authorize(Policies.Application)] [HttpPost("file/v2")] public async Task PostFile([FromBody] SendRequestModel model) { @@ -243,6 +330,7 @@ public class SendsController : Controller }; } + [Authorize(Policies.Application)] [HttpGet("{id}/file/{fileId}")] public async Task RenewFileUpload(string id, string fileId) { @@ -267,6 +355,7 @@ public class SendsController : Controller }; } + [Authorize(Policies.Application)] [HttpPost("{id}/file/{fileId}")] [SelfHosted(SelfHostedOnly = true)] [RequestSizeLimit(Constants.FileSize501mb)] @@ -283,12 +372,14 @@ public class SendsController : Controller { throw new BadRequestException("Could not locate send"); } + await Request.GetFileAsync(async (stream) => { await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send); }); } + [Authorize(Policies.Application)] [HttpPut("{id}")] public async Task Put(string id, [FromBody] SendRequestModel model) { @@ -304,6 +395,7 @@ public class SendsController : Controller return new SendResponseModel(send); } + [Authorize(Policies.Application)] [HttpPut("{id}/remove-password")] public async Task PutRemovePassword(string id) { @@ -322,6 +414,28 @@ public class SendsController : Controller return new SendResponseModel(send); } + // Removes ALL authentication (email or password) if any is present + [Authorize(Policies.Application)] + [HttpPut("{id}/remove-auth")] + public async Task PutRemoveAuth(string id) + { + var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found"); + var send = await _sendRepository.GetByIdAsync(new Guid(id)); + if (send == null || send.UserId != userId) + { + throw new NotFoundException(); + } + + // This endpoint exists because PUT preserves existing Password/Emails when not provided. + // This allows clients to update other fields without re-submitting sensitive auth data. + send.Password = null; + send.Emails = null; + send.AuthType = AuthType.None; + await _nonAnonymousSendCommand.SaveSendAsync(send); + return new SendResponseModel(send); + } + + [Authorize(Policies.Application)] [HttpDelete("{id}")] public async Task Delete(string id) { diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index cc92397269..7cf00621c1 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -235,16 +235,6 @@ public static class FeatureFlagKeys public const string SendUIRefresh = "pm-28175-send-ui-refresh"; public const string SendEmailOTP = "pm-19051-send-email-verification"; - /// - /// Enable this flag to output email/OTP authenticated sends from the `GET sends` endpoint. When - /// this flag is disabled, the `GET sends` endpoint omits email/OTP authenticated sends. - /// - /// - /// This flag is server-side only, and only inhibits the endpoint returning all sends. - /// Email/OTP sends can still be created and downloaded through other endpoints. - /// - public const string PM19051_ListEmailOtpSends = "tools-send-email-otp-listing"; - /* Vault Team */ public const string CipherKeyEncryption = "cipher-key-encryption"; public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk"; diff --git a/src/Core/Tools/Enums/AuthType.cs b/src/Core/Tools/Enums/AuthType.cs index 814ebf69b8..4a31275b7a 100644 --- a/src/Core/Tools/Enums/AuthType.cs +++ b/src/Core/Tools/Enums/AuthType.cs @@ -1,11 +1,8 @@ -using System.Text.Json.Serialization; - -namespace Bit.Core.Tools.Enums; +namespace Bit.Core.Tools.Enums; /// /// Specifies the authentication method required to access a Send. /// -[JsonConverter(typeof(JsonStringEnumConverter))] public enum AuthType : byte { /// diff --git a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs index fed7c9e8d4..97c2e64dc5 100644 --- a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs +++ b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs @@ -1,4 +1,5 @@ -using Bit.Core.Tools.Models.Data; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Models.Data; using Bit.Core.Tools.Repositories; using Bit.Core.Tools.SendFeatures.Queries.Interfaces; @@ -37,8 +38,8 @@ public class SendAuthenticationQuery : ISendAuthenticationQuery { null => NEVER_AUTHENTICATE, var s when s.AccessCount >= s.MaxAccessCount => NEVER_AUTHENTICATE, - var s when s.Emails is not null => emailOtp(s.Emails), - var s when s.Password is not null => new ResourcePassword(s.Password), + var s when s.AuthType == AuthType.Email && s.Emails is not null => emailOtp(s.Emails), + var s when s.AuthType == AuthType.Password && s.Password is not null => new ResourcePassword(s.Password), _ => NOT_AUTHENTICATED }; diff --git a/src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs b/src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs index cb539429a5..29bd8f56f9 100644 --- a/src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs +++ b/src/Core/Tools/SendFeatures/Queries/SendOwnerQuery.cs @@ -12,7 +12,6 @@ namespace Bit.Core.Tools.SendFeatures.Queries; public class SendOwnerQuery : ISendOwnerQuery { private readonly ISendRepository _repository; - private readonly IFeatureService _features; private readonly IUserService _users; /// @@ -24,10 +23,9 @@ public class SendOwnerQuery : ISendOwnerQuery /// /// Thrown when is . /// - public SendOwnerQuery(ISendRepository sendRepository, IFeatureService features, IUserService users) + public SendOwnerQuery(ISendRepository sendRepository, IUserService users) { _repository = sendRepository; - _features = features ?? throw new ArgumentNullException(nameof(features)); _users = users ?? throw new ArgumentNullException(nameof(users)); } @@ -51,16 +49,6 @@ public class SendOwnerQuery : ISendOwnerQuery var userId = _users.GetProperUserId(user) ?? throw new BadRequestException("invalid user."); var sends = await _repository.GetManyByUserIdAsync(userId); - var removeEmailOtp = !_features.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends); - if (removeEmailOtp) - { - // reify list to avoid invalidating the enumerator - foreach (var s in sends.Where(s => s.Emails != null).ToList()) - { - sends.Remove(s); - } - } - return sends; } } diff --git a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs index 541e8a4903..e3a9ba4435 100644 --- a/test/Api.Test/Tools/Controllers/SendsControllerTests.cs +++ b/test/Api.Test/Tools/Controllers/SendsControllerTests.cs @@ -8,8 +8,8 @@ using Bit.Api.Tools.Models.Request; using Bit.Api.Tools.Models.Response; using Bit.Core.Entities; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Services; -using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Data; @@ -28,7 +28,6 @@ namespace Bit.Api.Test.Tools.Controllers; public class SendsControllerTests : IDisposable { private readonly SendsController _sut; - private readonly GlobalSettings _globalSettings; private readonly IUserService _userService; private readonly ISendRepository _sendRepository; private readonly INonAnonymousSendCommand _nonAnonymousSendCommand; @@ -37,6 +36,8 @@ public class SendsControllerTests : IDisposable private readonly ISendAuthorizationService _sendAuthorizationService; private readonly ISendFileStorageService _sendFileStorageService; private readonly ILogger _logger; + private readonly IFeatureService _featureService; + private readonly IPushNotificationService _pushNotificationService; public SendsControllerTests() { @@ -47,8 +48,9 @@ public class SendsControllerTests : IDisposable _sendOwnerQuery = Substitute.For(); _sendAuthorizationService = Substitute.For(); _sendFileStorageService = Substitute.For(); - _globalSettings = new GlobalSettings(); _logger = Substitute.For>(); + _featureService = Substitute.For(); + _pushNotificationService = Substitute.For(); _sut = new SendsController( _sendRepository, @@ -59,7 +61,8 @@ public class SendsControllerTests : IDisposable _sendOwnerQuery, _sendFileStorageService, _logger, - _globalSettings + _featureService, + _pushNotificationService ); } @@ -96,8 +99,8 @@ public class SendsControllerTests : IDisposable { var now = DateTime.UtcNow; var expected = "You cannot have a Send with a deletion date that far " + - "into the future. Adjust the Deletion Date to a value less than 31 days from now " + - "and try again."; + "into the future. Adjust the Deletion Date to a value less than 31 days from now " + + "and try again."; var request = new SendRequestModel() { DeletionDate = now.AddDays(32) }; var exception = await Assert.ThrowsAsync(() => _sut.Post(request)); @@ -109,9 +112,10 @@ public class SendsControllerTests : IDisposable { var now = DateTime.UtcNow; var expected = "You cannot have a Send with a deletion date that far " + - "into the future. Adjust the Deletion Date to a value less than 31 days from now " + - "and try again."; - var request = new SendRequestModel() { Type = SendType.File, FileLength = 1024L, DeletionDate = now.AddDays(32) }; + "into the future. Adjust the Deletion Date to a value less than 31 days from now " + + "and try again."; + var request = + new SendRequestModel() { Type = SendType.File, FileLength = 1024L, DeletionDate = now.AddDays(32) }; var exception = await Assert.ThrowsAsync(() => _sut.PostFile(request)); Assert.Equal(expected, exception.Message); @@ -409,7 +413,8 @@ public class SendsControllerTests : IDisposable } [Theory, AutoData] - public async Task PutRemovePassword_WithWrongUser_ThrowsNotFoundException(Guid userId, Guid otherUserId, Guid sendId) + public async Task PutRemovePassword_WithWrongUser_ThrowsNotFoundException(Guid userId, Guid otherUserId, + Guid sendId) { _userService.GetProperUserId(Arg.Any()).Returns(userId); var existingSend = new Send @@ -753,4 +758,683 @@ public class SendsControllerTests : IDisposable s.Password == null && s.Emails == null)); } + + #region Authenticated Access Endpoints + + [Theory, AutoData] + public async Task AccessUsingAuth_WithValidSend_ReturnsSendAccessResponse(Guid sendId, User creator) + { + var send = new Send + { + Id = sendId, + UserId = creator.Id, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + HideEmail = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _userService.GetUserByIdAsync(creator.Id).Returns(creator); + + var result = await _sut.AccessUsingAuth(); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(CoreHelpers.Base64UrlEncode(sendId.ToByteArray()), response.Id); + Assert.Equal(creator.Email, response.CreatorIdentifier); + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _userService.Received(1).GetUserByIdAsync(creator.Id); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithHideEmail_DoesNotIncludeCreatorIdentifier(Guid sendId, User creator) + { + var send = new Send + { + Id = sendId, + UserId = creator.Id, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + HideEmail = true, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + var result = await _sut.AccessUsingAuth(); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(CoreHelpers.Base64UrlEncode(sendId.ToByteArray()), response.Id); + Assert.Null(response.CreatorIdentifier); + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any()); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithNoUserId_DoesNotIncludeCreatorIdentifier(Guid sendId) + { + var send = new Send + { + Id = sendId, + UserId = null, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + HideEmail = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + var result = await _sut.AccessUsingAuth(); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(CoreHelpers.Base64UrlEncode(sendId.ToByteArray()), response.Id); + Assert.Null(response.CreatorIdentifier); + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _userService.DidNotReceive().GetUserByIdAsync(Arg.Any()); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithNonExistentSend_ThrowsBadRequestException(Guid sendId) + { + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns((Send)null); + + var exception = + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + + Assert.Equal("Could not locate send", exception.Message); + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithFileSend_ReturnsCorrectResponse(Guid sendId, User creator) + { + var fileData = new SendFileData("Test File", "Notes", "document.pdf") { Id = "file-123", Size = 2048 }; + var send = new Send + { + Id = sendId, + UserId = creator.Id, + Type = SendType.File, + Data = JsonSerializer.Serialize(fileData), + HideEmail = false, + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _userService.GetUserByIdAsync(creator.Id).Returns(creator); + + var result = await _sut.AccessUsingAuth(); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(CoreHelpers.Base64UrlEncode(sendId.ToByteArray()), response.Id); + Assert.Equal(SendType.File, response.Type); + Assert.NotNull(response.File); + Assert.Equal("file-123", response.File.Id); + Assert.Equal(creator.Email, response.CreatorIdentifier); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithValidFileId_ReturnsDownloadUrl( + Guid sendId, string fileId, string expectedUrl) + { + var fileData = new SendFileData("Test File", "Notes", "document.pdf") { Id = fileId, Size = 2048 }; + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(fileData), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl); + + var result = await _sut.GetSendFileDownloadDataUsingAuth(fileId); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(fileId, response.Id); + Assert.Equal(expectedUrl, response.Url); + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _sendFileStorageService.Received(1).GetSendFileDownloadUrlAsync(send, fileId); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithNonExistentSend_ThrowsBadRequestException( + Guid sendId, string fileId) + { + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns((Send)null); + + var exception = + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + + Assert.Equal("Could not locate send", exception.Message); + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _sendFileStorageService.DidNotReceive() + .GetSendFileDownloadUrlAsync(Arg.Any(), Arg.Any()); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithTextSend_StillReturnsResponse( + Guid sendId, string fileId, string expectedUrl) + { + var send = new Send + { + Id = sendId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + _sendFileStorageService.GetSendFileDownloadUrlAsync(send, fileId).Returns(expectedUrl); + + var result = await _sut.GetSendFileDownloadDataUsingAuth(fileId); + + Assert.NotNull(result); + var objectResult = Assert.IsType(result); + var response = Assert.IsType(objectResult.Value); + Assert.Equal(fileId, response.Id); + Assert.Equal(expectedUrl, response.Url); + } + + #region AccessUsingAuth Validation Tests + + [Theory, AutoData] + public async Task AccessUsingAuth_WithExpiredSend_ThrowsNotFoundException(Guid sendId) + { + var send = new Send + { + Id = sendId, + UserId = Guid.NewGuid(), + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired yesterday + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithDeletedSend_ThrowsNotFoundException(Guid sendId) + { + var send = new Send + { + Id = sendId, + UserId = Guid.NewGuid(), + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + DeletionDate = DateTime.UtcNow.AddDays(-1), // Should have been deleted yesterday + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithDisabledSend_ThrowsNotFoundException(Guid sendId) + { + var send = new Send + { + Id = sendId, + UserId = Guid.NewGuid(), + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = true, // Disabled + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task AccessUsingAuth_WithAccessCountExceeded_ThrowsNotFoundException(Guid sendId) + { + var send = new Send + { + Id = sendId, + UserId = Guid.NewGuid(), + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 5, + MaxAccessCount = 5 // Limit reached + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.AccessUsingAuth()); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + #endregion + + #region GetSendFileDownloadDataUsingAuth Validation Tests + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithExpiredSend_ThrowsNotFoundException( + Guid sendId, string fileId) + { + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = DateTime.UtcNow.AddDays(-1), // Expired + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithDeletedSend_ThrowsNotFoundException( + Guid sendId, string fileId) + { + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), + DeletionDate = DateTime.UtcNow.AddDays(-1), // Deleted + ExpirationDate = null, + Disabled = false, + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithDisabledSend_ThrowsNotFoundException( + Guid sendId, string fileId) + { + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = true, // Disabled + AccessCount = 0, + MaxAccessCount = null + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + [Theory, AutoData] + public async Task GetSendFileDownloadDataUsingAuth_WithAccessCountExceeded_ThrowsNotFoundException( + Guid sendId, string fileId) + { + var send = new Send + { + Id = sendId, + Type = SendType.File, + Data = JsonSerializer.Serialize(new SendFileData("Test", "Notes", "file.pdf")), + DeletionDate = DateTime.UtcNow.AddDays(7), + ExpirationDate = null, + Disabled = false, + AccessCount = 10, + MaxAccessCount = 10 // Limit reached + }; + var user = CreateUserWithSendIdClaim(sendId); + _sut.ControllerContext = CreateControllerContextWithUser(user); + _sendRepository.GetByIdAsync(sendId).Returns(send); + + await Assert.ThrowsAsync(() => _sut.GetSendFileDownloadDataUsingAuth(fileId)); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + } + + #endregion + + #endregion + + #region PutRemoveAuth Tests + + [Theory, AutoData] + public async Task PutRemoveAuth_WithPasswordProtectedSend_RemovesPasswordAndSetsAuthTypeNone(Guid userId, + Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = "hashed-password", + Emails = null, + AuthType = AuthType.Password + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + Assert.Null(result.Password); + Assert.Null(result.Emails); + await _nonAnonymousSendCommand.Received(1).SaveSendAsync(Arg.Is(s => + s.Id == sendId && + s.Password == null && + s.Emails == null && + s.AuthType == AuthType.None)); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithEmailProtectedSend_RemovesEmailsAndSetsAuthTypeNone(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = null, + Emails = "test@example.com,user@example.com", + AuthType = AuthType.Email + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + Assert.Null(result.Password); + Assert.Null(result.Emails); + await _nonAnonymousSendCommand.Received(1).SaveSendAsync(Arg.Is(s => + s.Id == sendId && + s.Password == null && + s.Emails == null && + s.AuthType == AuthType.None)); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithSendAlreadyHavingNoAuth_StillSucceeds(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = null, + Emails = null, + AuthType = AuthType.None + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + Assert.Null(result.Password); + Assert.Null(result.Emails); + await _nonAnonymousSendCommand.Received(1).SaveSendAsync(Arg.Is(s => + s.Id == sendId && + s.Password == null && + s.Emails == null && + s.AuthType == AuthType.None)); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithFileSend_RemovesAuthAndPreservesFileData(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var fileData = new SendFileData("Test File", "Notes", "document.pdf") { Id = "file-123", Size = 2048 }; + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.File, + Data = JsonSerializer.Serialize(fileData), + Password = "hashed-password", + Emails = null, + AuthType = AuthType.Password + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + Assert.Equal(SendType.File, result.Type); + Assert.NotNull(result.File); + Assert.Equal("file-123", result.File.Id); + Assert.Null(result.Password); + Assert.Null(result.Emails); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithNonExistentSend_ThrowsNotFoundException(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + _sendRepository.GetByIdAsync(sendId).Returns((Send)null); + + await Assert.ThrowsAsync(() => _sut.PutRemoveAuth(sendId.ToString())); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _nonAnonymousSendCommand.DidNotReceive().SaveSendAsync(Arg.Any()); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithWrongUser_ThrowsNotFoundException(Guid userId, Guid otherUserId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var existingSend = new Send + { + Id = sendId, + UserId = otherUserId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = "hashed-password", + AuthType = AuthType.Password + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + await Assert.ThrowsAsync(() => _sut.PutRemoveAuth(sendId.ToString())); + + await _sendRepository.Received(1).GetByIdAsync(sendId); + await _nonAnonymousSendCommand.DidNotReceive().SaveSendAsync(Arg.Any()); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithNullUserId_ThrowsInvalidOperationException(Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns((Guid?)null); + + var exception = + await Assert.ThrowsAsync(() => _sut.PutRemoveAuth(sendId.ToString())); + + Assert.Equal("User ID not found", exception.Message); + await _sendRepository.DidNotReceive().GetByIdAsync(Arg.Any()); + await _nonAnonymousSendCommand.DidNotReceive().SaveSendAsync(Arg.Any()); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_WithSendHavingBothPasswordAndEmails_RemovesBoth(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = "hashed-password", + Emails = "test@example.com", + AuthType = AuthType.Password + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + Assert.Null(result.Password); + Assert.Null(result.Emails); + await _nonAnonymousSendCommand.Received(1).SaveSendAsync(Arg.Is(s => + s.Id == sendId && + s.Password == null && + s.Emails == null && + s.AuthType == AuthType.None)); + } + + [Theory, AutoData] + public async Task PutRemoveAuth_PreservesOtherSendProperties(Guid userId, Guid sendId) + { + _userService.GetProperUserId(Arg.Any()).Returns(userId); + var deletionDate = DateTime.UtcNow.AddDays(7); + var expirationDate = DateTime.UtcNow.AddDays(3); + var existingSend = new Send + { + Id = sendId, + UserId = userId, + Type = SendType.Text, + Data = JsonSerializer.Serialize(new SendTextData("Test", "Notes", "Text", false)), + Password = "hashed-password", + AuthType = AuthType.Password, + Key = "encryption-key", + MaxAccessCount = 10, + AccessCount = 3, + DeletionDate = deletionDate, + ExpirationDate = expirationDate, + Disabled = false, + HideEmail = true + }; + _sendRepository.GetByIdAsync(sendId).Returns(existingSend); + + var result = await _sut.PutRemoveAuth(sendId.ToString()); + + Assert.NotNull(result); + Assert.Equal(sendId, result.Id); + Assert.Equal(AuthType.None, result.AuthType); + // Verify other properties are preserved + Assert.Equal("encryption-key", result.Key); + Assert.Equal(10, result.MaxAccessCount); + Assert.Equal(3, result.AccessCount); + Assert.Equal(deletionDate, result.DeletionDate); + Assert.Equal(expirationDate, result.ExpirationDate); + Assert.False(result.Disabled); + Assert.True(result.HideEmail); + } + + #endregion + + #region Test Helpers + + private static ClaimsPrincipal CreateUserWithSendIdClaim(Guid sendId) + { + var claims = new List { new Claim("send_id", sendId.ToString()) }; + var identity = new ClaimsIdentity(claims, "TestAuth"); + return new ClaimsPrincipal(identity); + } + + private static ControllerContext CreateControllerContextWithUser(ClaimsPrincipal user) + { + return new ControllerContext { HttpContext = new Microsoft.AspNetCore.Http.DefaultHttpContext { User = user } }; + } + + #endregion } diff --git a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs index c34afc42bd..7901b3c5c0 100644 --- a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs +++ b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs @@ -1,4 +1,5 @@ using Bit.Core.Tools.Entities; +using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Data; using Bit.Core.Tools.Repositories; using Bit.Core.Tools.SendFeatures.Queries; @@ -47,7 +48,7 @@ public class SendAuthenticationQueryTests { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: emailString, password: null); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: emailString, password: null, AuthType.Email); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -63,7 +64,7 @@ public class SendAuthenticationQueryTests { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: "hashedpassword"); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: "hashedpassword", AuthType.Email); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -78,7 +79,7 @@ public class SendAuthenticationQueryTests { // Arrange var sendId = Guid.NewGuid(); - var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null); + var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None); _sendRepository.GetByIdAsync(sendId).Returns(send); // Act @@ -105,11 +106,11 @@ public class SendAuthenticationQueryTests public static IEnumerable AuthenticationMethodTestCases() { yield return new object[] { null, typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 5, maxAccessCount: 5, emails: null, password: null), typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 6, maxAccessCount: 5, emails: null, password: null), typeof(NeverAuthenticate) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: null), typeof(EmailOtp) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: "hashedpassword"), typeof(ResourcePassword) }; - yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null), typeof(NotAuthenticated) }; + yield return new object[] { CreateSend(accessCount: 5, maxAccessCount: 5, emails: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; + yield return new object[] { CreateSend(accessCount: 6, maxAccessCount: 5, emails: null, password: null, AuthType.None), typeof(NeverAuthenticate) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: "test@example.com", password: null, AuthType.Email), typeof(EmailOtp) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: "hashedpassword", AuthType.Password), typeof(ResourcePassword) }; + yield return new object[] { CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None), typeof(NotAuthenticated) }; } public static IEnumerable EmailParsingTestCases() @@ -121,7 +122,7 @@ public class SendAuthenticationQueryTests yield return new object[] { " , test@example.com, ,other@example.com, ", new[] { "test@example.com", "other@example.com" } }; } - private static Send CreateSend(int accessCount, int? maxAccessCount, string? emails, string? password) + private static Send CreateSend(int accessCount, int? maxAccessCount, string? emails, string? password, AuthType? authType) { return new Send { @@ -129,7 +130,8 @@ public class SendAuthenticationQueryTests AccessCount = accessCount, MaxAccessCount = maxAccessCount, Emails = emails, - Password = password + Password = password, + AuthType = authType }; } } diff --git a/test/Core.Test/Tools/Services/SendOwnerQueryTests.cs b/test/Core.Test/Tools/Services/SendOwnerQueryTests.cs index 9a2f942eb8..71ed27f2ac 100644 --- a/test/Core.Test/Tools/Services/SendOwnerQueryTests.cs +++ b/test/Core.Test/Tools/Services/SendOwnerQueryTests.cs @@ -12,7 +12,6 @@ namespace Bit.Core.Test.Tools.Services; public class SendOwnerQueryTests { private readonly ISendRepository _sendRepository; - private readonly IFeatureService _featureService; private readonly IUserService _userService; private readonly SendOwnerQuery _sendOwnerQuery; private readonly Guid _currentUserId = Guid.NewGuid(); @@ -21,11 +20,10 @@ public class SendOwnerQueryTests public SendOwnerQueryTests() { _sendRepository = Substitute.For(); - _featureService = Substitute.For(); _userService = Substitute.For(); _user = new ClaimsPrincipal(); _userService.GetProperUserId(_user).Returns(_currentUserId); - _sendOwnerQuery = new SendOwnerQuery(_sendRepository, _featureService, _userService); + _sendOwnerQuery = new SendOwnerQuery(_sendRepository, _userService); } [Fact] @@ -84,7 +82,7 @@ public class SendOwnerQueryTests } [Fact] - public async Task GetOwned_WithFeatureFlagEnabled_ReturnsAllSends() + public async Task GetOwned_ReturnsAllSendsIncludingEmailOTP() { // Arrange var sends = new List @@ -94,7 +92,6 @@ public class SendOwnerQueryTests CreateSend(Guid.NewGuid(), _currentUserId, emails: "other@example.com") }; _sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(sends); - _featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(true); // Act var result = await _sendOwnerQuery.GetOwned(_user); @@ -105,28 +102,6 @@ public class SendOwnerQueryTests Assert.Contains(sends[1], result); Assert.Contains(sends[2], result); await _sendRepository.Received(1).GetManyByUserIdAsync(_currentUserId); - _featureService.Received(1).IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends); - } - - [Fact] - public async Task GetOwned_WithFeatureFlagDisabled_FiltersOutEmailOtpSends() - { - // Arrange - var sendWithoutEmails = CreateSend(Guid.NewGuid(), _currentUserId, emails: null); - var sendWithEmails = CreateSend(Guid.NewGuid(), _currentUserId, emails: "test@example.com"); - var sends = new List { sendWithoutEmails, sendWithEmails }; - _sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(sends); - _featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(false); - - // Act - var result = await _sendOwnerQuery.GetOwned(_user); - - // Assert - Assert.Single(result); - Assert.Contains(sendWithoutEmails, result); - Assert.DoesNotContain(sendWithEmails, result); - await _sendRepository.Received(1).GetManyByUserIdAsync(_currentUserId); - _featureService.Received(1).IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends); } [Fact] @@ -147,7 +122,6 @@ public class SendOwnerQueryTests // Arrange var emptySends = new List(); _sendRepository.GetManyByUserIdAsync(_currentUserId).Returns(emptySends); - _featureService.IsEnabled(FeatureFlagKeys.PM19051_ListEmailOtpSends).Returns(true); // Act var result = await _sendOwnerQuery.GetOwned(_user); From 584af2ee3f41d0cf0b1cb9ccd9ca05ae4795792c Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:27:39 -0500 Subject: [PATCH 55/58] Catch general exception for all db types (#6846) * Switch `SqlException` to `DbException` Co-authored-by: rkac-bw <148072202+rkac-bw@users.noreply.github.com> * Fix CA2253 --------- Co-authored-by: rkac-bw <148072202+rkac-bw@users.noreply.github.com> --- .../HostedServices/DatabaseMigrationHostedService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Admin/HostedServices/DatabaseMigrationHostedService.cs b/src/Admin/HostedServices/DatabaseMigrationHostedService.cs index 219e6846bd..51739ce655 100644 --- a/src/Admin/HostedServices/DatabaseMigrationHostedService.cs +++ b/src/Admin/HostedServices/DatabaseMigrationHostedService.cs @@ -1,5 +1,5 @@ -using Bit.Core.Utilities; -using Microsoft.Data.SqlClient; +using System.Data.Common; +using Bit.Core.Utilities; namespace Bit.Admin.HostedServices; @@ -30,7 +30,7 @@ public class DatabaseMigrationHostedService : IHostedService, IDisposable // TODO: Maybe flip a flag somewhere to indicate migration is complete?? break; } - catch (SqlException e) + catch (DbException e) { if (i >= maxMigrationAttempts) { @@ -40,7 +40,7 @@ public class DatabaseMigrationHostedService : IHostedService, IDisposable else { _logger.LogError(e, - "Database unavailable for migration. Trying again (attempt #{0})...", i + 1); + "Database unavailable for migration. Trying again (attempt #{AttemptNumber})...", i + 1); await Task.Delay(20000, cancellationToken); } } From e22290c52b47c943ae109d81f203dcb2227c82c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:46:12 -0500 Subject: [PATCH 56/58] [deps] Auth: Update sass to v1.97.2 (#6630) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> --- bitwarden_license/src/Sso/package-lock.json | 8 ++++---- bitwarden_license/src/Sso/package.json | 2 +- src/Admin/package-lock.json | 8 ++++---- src/Admin/package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bitwarden_license/src/Sso/package-lock.json b/bitwarden_license/src/Sso/package-lock.json index f5e0468f87..c4a6d64231 100644 --- a/bitwarden_license/src/Sso/package-lock.json +++ b/bitwarden_license/src/Sso/package-lock.json @@ -17,7 +17,7 @@ "css-loader": "7.1.2", "expose-loader": "5.0.1", "mini-css-extract-plugin": "2.9.2", - "sass": "1.93.2", + "sass": "1.97.2", "sass-loader": "16.0.5", "webpack": "5.102.1", "webpack-cli": "5.1.4" @@ -1874,9 +1874,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.93.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", - "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "version": "1.97.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz", + "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", "dev": true, "license": "MIT", "peer": true, diff --git a/bitwarden_license/src/Sso/package.json b/bitwarden_license/src/Sso/package.json index df46444aca..31c66fdccf 100644 --- a/bitwarden_license/src/Sso/package.json +++ b/bitwarden_license/src/Sso/package.json @@ -16,7 +16,7 @@ "css-loader": "7.1.2", "expose-loader": "5.0.1", "mini-css-extract-plugin": "2.9.2", - "sass": "1.93.2", + "sass": "1.97.2", "sass-loader": "16.0.5", "webpack": "5.102.1", "webpack-cli": "5.1.4" diff --git a/src/Admin/package-lock.json b/src/Admin/package-lock.json index 6e0f78e1e6..d509cfc93f 100644 --- a/src/Admin/package-lock.json +++ b/src/Admin/package-lock.json @@ -18,7 +18,7 @@ "css-loader": "7.1.2", "expose-loader": "5.0.1", "mini-css-extract-plugin": "2.9.2", - "sass": "1.93.2", + "sass": "1.97.2", "sass-loader": "16.0.5", "webpack": "5.102.1", "webpack-cli": "5.1.4" @@ -1875,9 +1875,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.93.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", - "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "version": "1.97.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz", + "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", "dev": true, "license": "MIT", "peer": true, diff --git a/src/Admin/package.json b/src/Admin/package.json index f6f21e2cf9..5300df9369 100644 --- a/src/Admin/package.json +++ b/src/Admin/package.json @@ -17,7 +17,7 @@ "css-loader": "7.1.2", "expose-loader": "5.0.1", "mini-css-extract-plugin": "2.9.2", - "sass": "1.93.2", + "sass": "1.97.2", "sass-loader": "16.0.5", "webpack": "5.102.1", "webpack-cli": "5.1.4" From 21e9bb3138ba8d212c10ca01a106921657db5c12 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:50:37 -0500 Subject: [PATCH 57/58] feat(single sign-on) [PM-23572] Add Persistent Grants to SSO (#6636) * feat(sso-persisted-grants) [PM-23572]: Stub PersistedGrantStore. * feat(sso-persisted-grants) [PM-23572]: Update service reigtration with named cache. * feat(sso-persisted-grants) [PM-23572]: Add unit tests for DistributedCachePersistedGrantStore. * feat(sso-persisted-grants) [PM-23572]: Add additional tests. * feat(sso-persisted-grants) [PM-23572]: Add some additional clarifying comments on ExtendedCache vs InMemoryCaching for Duende. * feat(sso-persistent-grants) [PM-23572]: Spelling in a comment for cache key name. * feat(sso-persisted-grants) [PM-23572]: Add cache key constant and remove explicit skip distributed cache on set for default configuration. --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> --- .../DistributedCachePersistedGrantStore.cs | 102 +++++++ ...ersistedGrantsDistributedCacheConstants.cs | 10 + .../Utilities/ServiceCollectionExtensions.cs | 12 + ...istributedCachePersistedGrantStoreTests.cs | 257 ++++++++++++++++++ 4 files changed, 381 insertions(+) create mode 100644 bitwarden_license/src/Sso/IdentityServer/DistributedCachePersistedGrantStore.cs create mode 100644 bitwarden_license/src/Sso/Utilities/PersistedGrantsDistributedCacheConstants.cs create mode 100644 bitwarden_license/test/SSO.Test/IdentityServer/DistributedCachePersistedGrantStoreTests.cs diff --git a/bitwarden_license/src/Sso/IdentityServer/DistributedCachePersistedGrantStore.cs b/bitwarden_license/src/Sso/IdentityServer/DistributedCachePersistedGrantStore.cs new file mode 100644 index 0000000000..ecb2f36cec --- /dev/null +++ b/bitwarden_license/src/Sso/IdentityServer/DistributedCachePersistedGrantStore.cs @@ -0,0 +1,102 @@ +using Bit.Sso.Utilities; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Stores; +using ZiggyCreatures.Caching.Fusion; + +namespace Bit.Sso.IdentityServer; + +/// +/// Distributed cache-backed persisted grant store for short-lived grants. +/// Uses IFusionCache (which wraps IDistributedCache) for horizontal scaling support, +/// and fall back to in-memory caching if Redis is not configured. +/// Designed for SSO authorization codes which are short-lived (5 minutes) and single-use. +/// +/// +/// This is purposefully a different implementation from how Identity solves Persisted Grants. +/// Because even flavored grant store, e.g., AuthorizationCodeGrantStore, can add intermediary +/// logic to a grant's handling by type, the fact that they all wrap IdentityServer's IPersistedGrantStore +/// leans on IdentityServer's opinion that all grants, regardless of type, go to the same persistence +/// mechanism (cache, database). +/// +/// +public class DistributedCachePersistedGrantStore : IPersistedGrantStore +{ + private readonly IFusionCache _cache; + + public DistributedCachePersistedGrantStore( + [FromKeyedServices(PersistedGrantsDistributedCacheConstants.CacheKey)] IFusionCache cache) + { + _cache = cache; + } + + public async Task GetAsync(string key) + { + var result = await _cache.TryGetAsync(key); + + if (!result.HasValue) + { + return null; + } + + var grant = result.Value; + + // Check if grant has expired - remove expired grants from cache + if (grant.Expiration.HasValue && grant.Expiration.Value < DateTime.UtcNow) + { + await RemoveAsync(key); + return null; + } + + return grant; + } + + public Task> GetAllAsync(PersistedGrantFilter filter) + { + // Cache stores are key-value based and don't support querying by filter criteria. + // This method is typically used for cleanup operations on long-lived grants in databases. + // For SSO's short-lived authorization codes, we rely on TTL expiration instead. + + return Task.FromResult(Enumerable.Empty()); + } + + public Task RemoveAllAsync(PersistedGrantFilter filter) + { + // Revocation Strategy: SSO's logout flow (AccountController.LogoutAsync) only clears local + // authentication cookies and performs federated logout with external IdPs. It does not invoke + // Duende's EndSession or TokenRevocation endpoints. Authorization codes are single-use and expire + // within 5 minutes, making explicit revocation unnecessary for SSO's security model. + // https://docs.duendesoftware.com/identityserver/reference/stores/persisted-grant-store/ + + // Cache stores are key-value based and don't support bulk deletion by filter. + // This method is typically used for cleanup operations on long-lived grants in databases. + // For SSO's short-lived authorization codes, we rely on TTL expiration instead. + + return Task.FromResult(0); + } + + public async Task RemoveAsync(string key) + { + await _cache.RemoveAsync(key); + } + + public async Task StoreAsync(PersistedGrant grant) + { + // Calculate TTL based on grant expiration + var duration = grant.Expiration.HasValue + ? grant.Expiration.Value - DateTime.UtcNow + : TimeSpan.FromMinutes(5); // Default to 5 minutes if no expiration set + + // Ensure positive duration + if (duration <= TimeSpan.Zero) + { + return; + } + + // Cache key "sso-grants:" is configured by service registration. Going through the consumed KeyedService will + // give us a consistent cache key prefix for these grants. + await _cache.SetAsync( + grant.Key, + grant, + new FusionCacheEntryOptions { Duration = duration }); + } +} diff --git a/bitwarden_license/src/Sso/Utilities/PersistedGrantsDistributedCacheConstants.cs b/bitwarden_license/src/Sso/Utilities/PersistedGrantsDistributedCacheConstants.cs new file mode 100644 index 0000000000..3ec45377e3 --- /dev/null +++ b/bitwarden_license/src/Sso/Utilities/PersistedGrantsDistributedCacheConstants.cs @@ -0,0 +1,10 @@ +namespace Bit.Sso.Utilities; + +public static class PersistedGrantsDistributedCacheConstants +{ + /// + /// The SSO Persisted Grant cache key. Identifies the keyed service consumed by the SSO Persisted Grant Store as + /// well as the cache key/namespace for grant storage. + /// + public const string CacheKey = "sso-grants"; +} diff --git a/bitwarden_license/src/Sso/Utilities/ServiceCollectionExtensions.cs b/bitwarden_license/src/Sso/Utilities/ServiceCollectionExtensions.cs index a51a04f5c8..da7a79535e 100644 --- a/bitwarden_license/src/Sso/Utilities/ServiceCollectionExtensions.cs +++ b/bitwarden_license/src/Sso/Utilities/ServiceCollectionExtensions.cs @@ -9,6 +9,7 @@ using Bit.Sso.IdentityServer; using Bit.Sso.Models; using Duende.IdentityServer.Models; using Duende.IdentityServer.ResponseHandling; +using Duende.IdentityServer.Stores; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Sustainsys.Saml2.AspNetCore2; @@ -77,6 +78,17 @@ public static class ServiceCollectionExtensions }) .AddIdentityServerCertificate(env, globalSettings); + // PM-23572 + // Register named FusionCache for SSO authorization code grants. + // Provides separation of concerns and automatic Redis/in-memory negotiation + // .AddInMemoryCaching should still persist above; this handles configuration caching, etc., + // and is separate from this keyed service, which only serves grant negotiation. + services.AddExtendedCache(PersistedGrantsDistributedCacheConstants.CacheKey, globalSettings); + + // Store authorization codes in distributed cache for horizontal scaling + // Uses named FusionCache which gracefully degrades to in-memory when Redis isn't configured + services.AddSingleton(); + return identityServerBuilder; } } diff --git a/bitwarden_license/test/SSO.Test/IdentityServer/DistributedCachePersistedGrantStoreTests.cs b/bitwarden_license/test/SSO.Test/IdentityServer/DistributedCachePersistedGrantStoreTests.cs new file mode 100644 index 0000000000..c0aa93f068 --- /dev/null +++ b/bitwarden_license/test/SSO.Test/IdentityServer/DistributedCachePersistedGrantStoreTests.cs @@ -0,0 +1,257 @@ +using Bit.Sso.IdentityServer; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Stores; +using NSubstitute; +using ZiggyCreatures.Caching.Fusion; + +namespace Bit.SSO.Test.IdentityServer; + +public class DistributedCachePersistedGrantStoreTests +{ + private readonly IFusionCache _cache; + private readonly DistributedCachePersistedGrantStore _sut; + + public DistributedCachePersistedGrantStoreTests() + { + _cache = Substitute.For(); + _sut = new DistributedCachePersistedGrantStore(_cache); + } + + [Fact] + public async Task StoreAsync_StoresGrantWithCalculatedTTL() + { + // Arrange + var grant = CreateTestGrant("test-key", expiration: DateTime.UtcNow.AddMinutes(5)); + + // Act + await _sut.StoreAsync(grant); + + // Assert + await _cache.Received(1).SetAsync( + "test-key", + grant, + Arg.Is(opts => + opts.Duration >= TimeSpan.FromMinutes(4.9) && + opts.Duration <= TimeSpan.FromMinutes(5))); + } + + [Fact] + public async Task StoreAsync_WithNoExpiration_UsesDefaultFiveMinuteTTL() + { + // Arrange + var grant = CreateTestGrant("no-expiry-key", expiration: null); + + // Act + await _sut.StoreAsync(grant); + + // Assert + await _cache.Received(1).SetAsync( + "no-expiry-key", + grant, + Arg.Is(opts => opts.Duration == TimeSpan.FromMinutes(5))); + } + + [Fact] + public async Task StoreAsync_WithAlreadyExpiredGrant_DoesNotStore() + { + // Arrange + var expiredGrant = CreateTestGrant("expired-key", expiration: DateTime.UtcNow.AddMinutes(-1)); + + // Act + await _sut.StoreAsync(expiredGrant); + + // Assert + await _cache.DidNotReceive().SetAsync( + Arg.Any(), + Arg.Any(), + Arg.Any()); + } + + [Fact] + public async Task StoreAsync_EnablesDistributedCache() + { + // Arrange + var grant = CreateTestGrant("distributed-key", expiration: DateTime.UtcNow.AddMinutes(5)); + + // Act + await _sut.StoreAsync(grant); + + // Assert + await _cache.Received(1).SetAsync( + "distributed-key", + grant, + Arg.Is(opts => + opts.SkipDistributedCache == false && + opts.SkipDistributedCacheReadWhenStale == false)); + } + + [Fact] + public async Task GetAsync_WithValidGrant_ReturnsGrant() + { + // Arrange + var grant = CreateTestGrant("valid-key", expiration: DateTime.UtcNow.AddMinutes(5)); + _cache.TryGetAsync("valid-key") + .Returns(MaybeValue.FromValue(grant)); + + // Act + var result = await _sut.GetAsync("valid-key"); + + // Assert + Assert.NotNull(result); + Assert.Equal("valid-key", result.Key); + Assert.Equal("authorization_code", result.Type); + Assert.Equal("test-subject", result.SubjectId); + await _cache.DidNotReceive().RemoveAsync(Arg.Any()); + } + + [Fact] + public async Task GetAsync_WithNonExistentKey_ReturnsNull() + { + // Arrange + _cache.TryGetAsync("nonexistent-key") + .Returns(MaybeValue.None); + + // Act + var result = await _sut.GetAsync("nonexistent-key"); + + // Assert + Assert.Null(result); + await _cache.DidNotReceive().RemoveAsync(Arg.Any()); + } + + [Fact] + public async Task GetAsync_WithExpiredGrant_RemovesAndReturnsNull() + { + // Arrange + var expiredGrant = CreateTestGrant("expired-key", expiration: DateTime.UtcNow.AddMinutes(-1)); + _cache.TryGetAsync("expired-key") + .Returns(MaybeValue.FromValue(expiredGrant)); + + // Act + var result = await _sut.GetAsync("expired-key"); + + // Assert + Assert.Null(result); + await _cache.Received(1).RemoveAsync("expired-key"); + } + + [Fact] + public async Task GetAsync_WithNoExpiration_ReturnsGrant() + { + // Arrange + var grant = CreateTestGrant("no-expiry-key", expiration: null); + _cache.TryGetAsync("no-expiry-key") + .Returns(MaybeValue.FromValue(grant)); + + // Act + var result = await _sut.GetAsync("no-expiry-key"); + + // Assert + Assert.NotNull(result); + Assert.Equal("no-expiry-key", result.Key); + Assert.Null(result.Expiration); + await _cache.DidNotReceive().RemoveAsync(Arg.Any()); + } + + [Fact] + public async Task RemoveAsync_RemovesGrantFromCache() + { + // Act + await _sut.RemoveAsync("remove-key"); + + // Assert + await _cache.Received(1).RemoveAsync("remove-key"); + } + + [Fact] + public async Task GetAllAsync_ReturnsEmptyCollection() + { + // Arrange + var filter = new PersistedGrantFilter + { + SubjectId = "test-subject", + SessionId = "test-session", + ClientId = "test-client", + Type = "authorization_code" + }; + + // Act + var result = await _sut.GetAllAsync(filter); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task RemoveAllAsync_CompletesWithoutError() + { + // Arrange + var filter = new PersistedGrantFilter + { + SubjectId = "test-subject", + ClientId = "test-client" + }; + + // Act & Assert - should not throw + await _sut.RemoveAllAsync(filter); + + // Verify no cache operations were performed + await _cache.DidNotReceive().RemoveAsync(Arg.Any()); + } + + [Fact] + public async Task StoreAsync_PreservesAllGrantProperties() + { + // Arrange + var grant = new PersistedGrant + { + Key = "full-grant-key", + Type = "authorization_code", + SubjectId = "user-123", + SessionId = "session-456", + ClientId = "client-789", + Description = "Test grant", + CreationTime = DateTime.UtcNow.AddMinutes(-1), + Expiration = DateTime.UtcNow.AddMinutes(5), + ConsumedTime = null, + Data = "{\"test\":\"data\"}" + }; + + PersistedGrant? capturedGrant = null; + await _cache.SetAsync( + Arg.Any(), + Arg.Do(g => capturedGrant = g), + Arg.Any()); + + // Act + await _sut.StoreAsync(grant); + + // Assert + Assert.NotNull(capturedGrant); + Assert.Equal(grant.Key, capturedGrant.Key); + Assert.Equal(grant.Type, capturedGrant.Type); + Assert.Equal(grant.SubjectId, capturedGrant.SubjectId); + Assert.Equal(grant.SessionId, capturedGrant.SessionId); + Assert.Equal(grant.ClientId, capturedGrant.ClientId); + Assert.Equal(grant.Description, capturedGrant.Description); + Assert.Equal(grant.CreationTime, capturedGrant.CreationTime); + Assert.Equal(grant.Expiration, capturedGrant.Expiration); + Assert.Equal(grant.ConsumedTime, capturedGrant.ConsumedTime); + Assert.Equal(grant.Data, capturedGrant.Data); + } + + private static PersistedGrant CreateTestGrant(string key, DateTime? expiration) + { + return new PersistedGrant + { + Key = key, + Type = "authorization_code", + SubjectId = "test-subject", + ClientId = "test-client", + CreationTime = DateTime.UtcNow, + Expiration = expiration, + Data = "{\"test\":\"data\"}" + }; + } +} From 9116a0b3fcfa111f02ed42c68ead1237e25ec14e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:22:28 -0500 Subject: [PATCH 58/58] [deps] Auth: Update webpack to v5.104.1 (#6701) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> --- bitwarden_license/src/Sso/package-lock.json | 86 +++++++++++---------- bitwarden_license/src/Sso/package.json | 2 +- src/Admin/package-lock.json | 86 +++++++++++---------- src/Admin/package.json | 2 +- 4 files changed, 92 insertions(+), 84 deletions(-) diff --git a/bitwarden_license/src/Sso/package-lock.json b/bitwarden_license/src/Sso/package-lock.json index c4a6d64231..efeee7f4ca 100644 --- a/bitwarden_license/src/Sso/package-lock.json +++ b/bitwarden_license/src/Sso/package-lock.json @@ -19,7 +19,7 @@ "mini-css-extract-plugin": "2.9.2", "sass": "1.97.2", "sass-loader": "16.0.5", - "webpack": "5.102.1", + "webpack": "5.104.1", "webpack-cli": "5.1.4" } }, @@ -749,9 +749,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz", - "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==", + "version": "2.9.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.13.tgz", + "integrity": "sha512-WhtvB2NG2wjr04+h77sg3klAIwrgOqnjS49GGudnUPGFFgg7G17y7Qecqp+2Dr5kUDxNRBca0SK7cG8JwzkWDQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -792,9 +792,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -813,11 +813,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -834,9 +834,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001763", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz", + "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==", "dev": true, "funding": [ { @@ -988,9 +988,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -1022,9 +1022,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -1418,13 +1418,17 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { @@ -1541,9 +1545,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -2109,9 +2113,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2165,9 +2169,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -2217,9 +2221,9 @@ } }, "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", "peer": true, @@ -2232,21 +2236,21 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, diff --git a/bitwarden_license/src/Sso/package.json b/bitwarden_license/src/Sso/package.json index 31c66fdccf..b0a1849421 100644 --- a/bitwarden_license/src/Sso/package.json +++ b/bitwarden_license/src/Sso/package.json @@ -18,7 +18,7 @@ "mini-css-extract-plugin": "2.9.2", "sass": "1.97.2", "sass-loader": "16.0.5", - "webpack": "5.102.1", + "webpack": "5.104.1", "webpack-cli": "5.1.4" } } diff --git a/src/Admin/package-lock.json b/src/Admin/package-lock.json index d509cfc93f..e851daac36 100644 --- a/src/Admin/package-lock.json +++ b/src/Admin/package-lock.json @@ -20,7 +20,7 @@ "mini-css-extract-plugin": "2.9.2", "sass": "1.97.2", "sass-loader": "16.0.5", - "webpack": "5.102.1", + "webpack": "5.104.1", "webpack-cli": "5.1.4" } }, @@ -750,9 +750,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz", - "integrity": "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==", + "version": "2.9.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.13.tgz", + "integrity": "sha512-WhtvB2NG2wjr04+h77sg3klAIwrgOqnjS49GGudnUPGFFgg7G17y7Qecqp+2Dr5kUDxNRBca0SK7cG8JwzkWDQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -793,9 +793,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -814,11 +814,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -835,9 +835,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001751", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "version": "1.0.30001763", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz", + "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==", "dev": true, "funding": [ { @@ -989,9 +989,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.237", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", - "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -1023,9 +1023,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -1419,13 +1419,17 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { @@ -1542,9 +1546,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -2110,9 +2114,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2174,9 +2178,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -2226,9 +2230,9 @@ } }, "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", "peer": true, @@ -2241,21 +2245,21 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, diff --git a/src/Admin/package.json b/src/Admin/package.json index 5300df9369..3a3926d6ee 100644 --- a/src/Admin/package.json +++ b/src/Admin/package.json @@ -19,7 +19,7 @@ "mini-css-extract-plugin": "2.9.2", "sass": "1.97.2", "sass-loader": "16.0.5", - "webpack": "5.102.1", + "webpack": "5.104.1", "webpack-cli": "5.1.4" } }