diff --git a/.editorconfig b/.editorconfig index 21d7ac4a3a..fd68808456 100644 --- a/.editorconfig +++ b/.editorconfig @@ -123,3 +123,12 @@ csharp_style_namespace_declarations = file_scoped:warning # Switch expression dotnet_diagnostic.CS8509.severity = error # missing switch case for named enum value dotnet_diagnostic.CS8524.severity = none # missing switch case for unnamed enum value + +# CA2253: Named placeholders should nto be numeric values +dotnet_diagnostic.CA2253.severity = suggestion + +# CA2254: Template should be a static expression +dotnet_diagnostic.CA2254.severity = warning + +# CA1727: Use PascalCase for named placeholders +dotnet_diagnostic.CA1727.severity = suggestion diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6db4905fec..44c7cfdf8c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -96,6 +96,9 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev # The PushType enum is expected to be editted by anyone without need for Platform review src/Core/Platform/Push/PushType.cs +# SDK +util/RustSdk @bitwarden/team-sdk-sme + # Multiple owners - DO NOT REMOVE (BRE) **/packages.lock.json Directory.Build.props diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b8ef5704d3..5cf7aa29aa 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -10,6 +10,11 @@ "nuget", ], packageRules: [ + { + groupName: "cargo minor", + matchManagers: ["cargo"], + matchUpdateTypes: ["minor"], + }, { groupName: "dockerfile minor", matchManagers: ["dockerfile"], diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe82f9fbe6..907f50197b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Verify format run: dotnet format --verify-no-changes @@ -117,7 +117,7 @@ jobs: fi - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Set up Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -269,7 +269,7 @@ jobs: - name: Scan Docker image id: container-scan - uses: anchore/scan-action@2c901ab7378897c01b8efaa2d0c9bf519cc64b9e # v6.2.0 + uses: anchore/scan-action@f6601287cdb1efc985d6b765bbf99cb4c0ac29d8 # v7.0.0 with: image: ${{ steps.image-tags.outputs.primary_tag }} fail-build: false @@ -299,7 +299,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main @@ -425,7 +425,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Print environment run: | diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index 6bbc33299f..cdba344195 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Restore tools run: dotnet tool restore @@ -179,7 +179,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 - name: Print environment run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 718616c47b..422747efdd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,15 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 + + - name: Install rust + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable + with: + toolchain: stable + + - name: Cache cargo registry + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 - name: Install rust uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable diff --git a/.gitignore b/.gitignore index 2712ff449d..5a708ede30 100644 --- a/.gitignore +++ b/.gitignore @@ -234,3 +234,4 @@ bitwarden_license/src/Sso/Sso.zip /identity.json /api.json /api.public.json +.serena/ diff --git a/Directory.Build.props b/Directory.Build.props index 4aa72f3e81..84b8dd22be 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net8.0 - 2025.10.0 + 2025.10.1 Bit.$(MSBuildProjectName) enable diff --git a/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs b/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs index 9ade2d660a..994b305349 100644 --- a/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs +++ b/bitwarden_license/src/Commercial.Core/AdminConsole/Providers/RemoveOrganizationFromProviderCommand.cs @@ -148,22 +148,30 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv } else if (organization.IsStripeEnabled()) { - var subscription = await _stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId); + var subscription = await _stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId, new SubscriptionGetOptions + { + Expand = ["customer"] + }); + if (subscription.Status is StripeConstants.SubscriptionStatus.Canceled or StripeConstants.SubscriptionStatus.IncompleteExpired) { return; } - await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions + await _stripeAdapter.CustomerUpdateAsync(subscription.CustomerId, new CustomerUpdateOptions { - Coupon = string.Empty, Email = organization.BillingEmail }); + if (subscription.Customer.Discount?.Coupon != null) + { + await _stripeAdapter.CustomerDeleteDiscountAsync(subscription.CustomerId); + } + await _stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId, new SubscriptionUpdateOptions { CollectionMethod = StripeConstants.CollectionMethod.SendInvoice, - DaysUntilDue = 30 + DaysUntilDue = 30, }); await _subscriberService.RemovePaymentSource(organization); diff --git a/bitwarden_license/src/Commercial.Core/Billing/Providers/Services/ProviderBillingService.cs b/bitwarden_license/src/Commercial.Core/Billing/Providers/Services/ProviderBillingService.cs index c9851eb403..e352297f1e 100644 --- a/bitwarden_license/src/Commercial.Core/Billing/Providers/Services/ProviderBillingService.cs +++ b/bitwarden_license/src/Commercial.Core/Billing/Providers/Services/ProviderBillingService.cs @@ -481,7 +481,6 @@ public class ProviderBillingService( City = billingAddress.City, State = billingAddress.State }, - Coupon = !string.IsNullOrEmpty(provider.DiscountId) ? provider.DiscountId : null, Description = provider.DisplayBusinessName(), Email = provider.BillingEmail, InvoiceSettings = new CustomerInvoiceSettingsOptions @@ -663,6 +662,7 @@ public class ProviderBillingService( : CollectionMethod.SendInvoice, Customer = customer.Id, DaysUntilDue = usePaymentMethod ? null : 30, + Discounts = !string.IsNullOrEmpty(provider.DiscountId) ? [new SubscriptionDiscountOptions { Coupon = provider.DiscountId }] : null, Items = subscriptionItemOptionsList, Metadata = new Dictionary { { "providerId", provider.Id.ToString() } }, OffSession = true, @@ -671,7 +671,6 @@ public class ProviderBillingService( AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } }; - try { var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); diff --git a/bitwarden_license/src/Sso/Startup.cs b/bitwarden_license/src/Sso/Startup.cs index 3aeb9c6beb..3ae8883ac4 100644 --- a/bitwarden_license/src/Sso/Startup.cs +++ b/bitwarden_license/src/Sso/Startup.cs @@ -157,6 +157,6 @@ public class Startup app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); // Log startup - logger.LogInformation(Constants.BypassFiltersEventId, globalSettings.ProjectName + " started."); + logger.LogInformation(Constants.BypassFiltersEventId, "{Project} started.", globalSettings.ProjectName); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs index 9b9c41048b..2bb02c3cee 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs @@ -156,16 +156,18 @@ public class RemoveOrganizationFromProviderCommandTests "b@example.com" ]); - sutProvider.GetDependency().SubscriptionGetAsync(organization.GatewaySubscriptionId) - .Returns(GetSubscription(organization.GatewaySubscriptionId)); + sutProvider.GetDependency().SubscriptionGetAsync(organization.GatewaySubscriptionId, Arg.Is( + options => options.Expand.Contains("customer"))) + .Returns(GetSubscription(organization.GatewaySubscriptionId, organization.GatewayCustomerId)); await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization); var stripeAdapter = sutProvider.GetDependency(); await stripeAdapter.Received(1).CustomerUpdateAsync(organization.GatewayCustomerId, - Arg.Is(options => - options.Coupon == string.Empty && options.Email == "a@example.com")); + Arg.Is(options => options.Email == "a@example.com")); + + await stripeAdapter.Received(1).CustomerDeleteDiscountAsync(organization.GatewayCustomerId); await stripeAdapter.Received(1).SubscriptionUpdateAsync(organization.GatewaySubscriptionId, Arg.Is(options => @@ -368,10 +370,21 @@ public class RemoveOrganizationFromProviderCommandTests Arg.Is>(emails => emails.FirstOrDefault() == "a@example.com")); } - private static Subscription GetSubscription(string subscriptionId) => + private static Subscription GetSubscription(string subscriptionId, string customerId) => new() { Id = subscriptionId, + CustomerId = customerId, + Customer = new Customer + { + Discount = new Discount + { + Coupon = new Coupon + { + Id = "coupon-id" + } + } + }, Status = StripeConstants.SubscriptionStatus.Active, Items = new StripeList { diff --git a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs index 1b02e62970..8816885ea7 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs @@ -436,7 +436,7 @@ public class PatchGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); // Assert: logging - sutProvider.GetDependency>().ReceivedWithAnyArgs().LogWarning(default); + sutProvider.GetDependency>().ReceivedWithAnyArgs().LogWarning(""); } [Theory] diff --git a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs index 2417bf610d..0d992cb96a 100644 --- a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs @@ -472,6 +472,7 @@ public class OrganizationsController : Controller organization.UseRiskInsights = model.UseRiskInsights; organization.UseOrganizationDomains = model.UseOrganizationDomains; organization.UseAdminSponsoredFamilies = model.UseAdminSponsoredFamilies; + organization.UseAutomaticUserConfirmation = model.UseAutomaticUserConfirmation; //secrets organization.SmSeats = model.SmSeats; diff --git a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs index b64af3135f..6059a003b6 100644 --- a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs @@ -106,6 +106,8 @@ public class OrganizationEditModel : OrganizationViewModel SmServiceAccounts = org.SmServiceAccounts; MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts; UseOrganizationDomains = org.UseOrganizationDomains; + UseAutomaticUserConfirmation = org.UseAutomaticUserConfirmation; + _plans = plans; } @@ -192,6 +194,8 @@ public class OrganizationEditModel : OrganizationViewModel [Display(Name = "Use Organization Domains")] public bool UseOrganizationDomains { get; set; } + [Display(Name = "Automatic User Confirmation")] + public bool UseAutomaticUserConfirmation { get; set; } /** * Creates a Plan[] object for use in Javascript * This is mapped manually below to provide some type safety in case the plan objects change @@ -231,6 +235,7 @@ public class OrganizationEditModel : OrganizationViewModel LegacyYear = p.LegacyYear, Disabled = p.Disabled, SupportsSecretsManager = p.SupportsSecretsManager, + AutomaticUserConfirmation = p.AutomaticUserConfirmation, PasswordManager = new { diff --git a/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml b/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml index 267264a38f..06ae5b03b3 100644 --- a/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml +++ b/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml @@ -159,6 +159,13 @@ } + @if(FeatureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)) + { +
+ + +
+ }

Password Manager

diff --git a/src/Admin/Billing/Controllers/MigrateProvidersController.cs b/src/Admin/Billing/Controllers/MigrateProvidersController.cs deleted file mode 100644 index ef5ea2312e..0000000000 --- a/src/Admin/Billing/Controllers/MigrateProvidersController.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Bit.Admin.Billing.Models; -using Bit.Admin.Enums; -using Bit.Admin.Utilities; -using Bit.Core.Billing.Providers.Migration.Models; -using Bit.Core.Billing.Providers.Migration.Services; -using Bit.Core.Utilities; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Bit.Admin.Billing.Controllers; - -[Authorize] -[Route("migrate-providers")] -[SelfHosted(NotSelfHostedOnly = true)] -public class MigrateProvidersController( - IProviderMigrator providerMigrator) : Controller -{ - [HttpGet] - [RequirePermission(Permission.Tools_MigrateProviders)] - public IActionResult Index() - { - return View(new MigrateProvidersRequestModel()); - } - - [HttpPost] - [RequirePermission(Permission.Tools_MigrateProviders)] - [ValidateAntiForgeryToken] - public async Task PostAsync(MigrateProvidersRequestModel request) - { - var providerIds = GetProviderIdsFromInput(request.ProviderIds); - - if (providerIds.Count == 0) - { - return RedirectToAction("Index"); - } - - foreach (var providerId in providerIds) - { - await providerMigrator.Migrate(providerId); - } - - return RedirectToAction("Results", new { ProviderIds = string.Join("\r\n", providerIds) }); - } - - [HttpGet("results")] - [RequirePermission(Permission.Tools_MigrateProviders)] - public async Task ResultsAsync(MigrateProvidersRequestModel request) - { - var providerIds = GetProviderIdsFromInput(request.ProviderIds); - - if (providerIds.Count == 0) - { - return View(Array.Empty()); - } - - var results = await Task.WhenAll(providerIds.Select(providerMigrator.GetResult)); - - return View(results); - } - - [HttpGet("results/{providerId:guid}")] - [RequirePermission(Permission.Tools_MigrateProviders)] - public async Task DetailsAsync([FromRoute] Guid providerId) - { - var result = await providerMigrator.GetResult(providerId); - - if (result == null) - { - return RedirectToAction("Index"); - } - - return View(result); - } - - private static List GetProviderIdsFromInput(string text) => !string.IsNullOrEmpty(text) - ? text.Split( - ["\r\n", "\r", "\n"], - StringSplitOptions.TrimEntries - ) - .Select(id => new Guid(id)) - .ToList() - : []; -} diff --git a/src/Admin/Billing/Models/MigrateProvidersRequestModel.cs b/src/Admin/Billing/Models/MigrateProvidersRequestModel.cs deleted file mode 100644 index 273f934eba..0000000000 --- a/src/Admin/Billing/Models/MigrateProvidersRequestModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using System.ComponentModel.DataAnnotations; - -namespace Bit.Admin.Billing.Models; - -public class MigrateProvidersRequestModel -{ - [Required] - [Display(Name = "Provider IDs")] - public string ProviderIds { get; set; } -} diff --git a/src/Admin/Billing/Views/MigrateProviders/Details.cshtml b/src/Admin/Billing/Views/MigrateProviders/Details.cshtml deleted file mode 100644 index 6ee0344057..0000000000 --- a/src/Admin/Billing/Views/MigrateProviders/Details.cshtml +++ /dev/null @@ -1,39 +0,0 @@ -@using System.Text.Json -@model Bit.Core.Billing.Providers.Migration.Models.ProviderMigrationResult -@{ - ViewData["Title"] = "Results"; -} - -

Migrate Providers

-

Migration Details: @Model.ProviderName

-
-
Id
-
@Model.ProviderId
- -
Result
-
@Model.Result
-
-

Client Organizations

-
- - - - - - - - - - - @foreach (var clientResult in Model.Clients) - { - - - - - - - } - -
IDNameResultPrevious State
@clientResult.OrganizationId@clientResult.OrganizationName@clientResult.Result
@Html.Raw(JsonSerializer.Serialize(clientResult.PreviousState))
-
diff --git a/src/Admin/Billing/Views/MigrateProviders/Index.cshtml b/src/Admin/Billing/Views/MigrateProviders/Index.cshtml deleted file mode 100644 index 0aed94c25d..0000000000 --- a/src/Admin/Billing/Views/MigrateProviders/Index.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model Bit.Admin.Billing.Models.MigrateProvidersRequestModel; -@{ - ViewData["Title"] = "Migrate Providers"; -} - -

Migrate Providers

-

Bulk Consolidated Billing Migration Tool

-
-

- This tool allows you to provide a list of IDs for Providers that you would like to migrate to Consolidated Billing. - Because of the expensive nature of the operation, you can only migrate 10 Providers at a time. -

-

- Updates made through this tool are irreversible without manual intervention. -

-

Example Input (Please enter each Provider ID separated by a new line):

-
-
-
f513affc-2290-4336-879e-21ec3ecf3e78
-f7a5cb0d-4b74-445c-8d8c-232d1d32bbe2
-bf82d3cf-0e21-4f39-b81b-ef52b2fc6a3a
-174e82fc-70c3-448d-9fe7-00bad2a3ab00
-22a4bbbf-58e3-4e4c-a86a-a0d7caf4ff14
-
-
-
-
-
- - -
-
- -
-
-
-
-
- - -
-
- -
-
-
diff --git a/src/Admin/Billing/Views/MigrateProviders/Results.cshtml b/src/Admin/Billing/Views/MigrateProviders/Results.cshtml deleted file mode 100644 index 94db08db3d..0000000000 --- a/src/Admin/Billing/Views/MigrateProviders/Results.cshtml +++ /dev/null @@ -1,28 +0,0 @@ -@model Bit.Core.Billing.Providers.Migration.Models.ProviderMigrationResult[] -@{ - ViewData["Title"] = "Results"; -} - -

Migrate Providers

-

Results

-
- - - - - - - - - - @foreach (var result in Model) - { - - - - - - } - -
IDNameResult
@result.ProviderId@result.ProviderName@result.Result
-
diff --git a/src/Admin/Controllers/HomeController.cs b/src/Admin/Controllers/HomeController.cs index debe5979f5..5b36032ec9 100644 --- a/src/Admin/Controllers/HomeController.cs +++ b/src/Admin/Controllers/HomeController.cs @@ -61,7 +61,7 @@ public class HomeController : Controller } catch (HttpRequestException e) { - _logger.LogError(e, $"Error encountered while sending GET request to {requestUri}"); + _logger.LogError(e, "Error encountered while sending GET request to {RequestUri}", requestUri); return new JsonResult("Unable to fetch latest version") { StatusCode = StatusCodes.Status500InternalServerError }; } @@ -83,7 +83,7 @@ public class HomeController : Controller } catch (HttpRequestException e) { - _logger.LogError(e, $"Error encountered while sending GET request to {requestUri}"); + _logger.LogError(e, "Error encountered while sending GET request to {RequestUri}", requestUri); return new JsonResult("Unable to fetch installed version") { StatusCode = StatusCodes.Status500InternalServerError }; } diff --git a/src/Admin/Controllers/ToolsController.cs b/src/Admin/Controllers/ToolsController.cs index b754b1f968..46dafd65e7 100644 --- a/src/Admin/Controllers/ToolsController.cs +++ b/src/Admin/Controllers/ToolsController.cs @@ -1,7 +1,6 @@ // FIXME: Update this file to be null safe and then delete the line below #nullable disable -using System.Text; using System.Text.Json; using Bit.Admin.Enums; using Bit.Admin.Models; @@ -10,7 +9,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Organizations.Queries; using Bit.Core.Entities; -using Bit.Core.Models.BitStripe; using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.Services; @@ -33,7 +31,6 @@ public class ToolsController : Controller private readonly IInstallationRepository _installationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IProviderUserRepository _providerUserRepository; - private readonly IPaymentService _paymentService; private readonly IStripeAdapter _stripeAdapter; private readonly IWebHostEnvironment _environment; @@ -46,7 +43,6 @@ public class ToolsController : Controller IInstallationRepository installationRepository, IOrganizationUserRepository organizationUserRepository, IProviderUserRepository providerUserRepository, - IPaymentService paymentService, IStripeAdapter stripeAdapter, IWebHostEnvironment environment) { @@ -58,7 +54,6 @@ public class ToolsController : Controller _installationRepository = installationRepository; _organizationUserRepository = organizationUserRepository; _providerUserRepository = providerUserRepository; - _paymentService = paymentService; _stripeAdapter = stripeAdapter; _environment = environment; } @@ -341,138 +336,4 @@ public class ToolsController : Controller throw new Exception("No license to generate."); } } - - [RequirePermission(Permission.Tools_ManageStripeSubscriptions)] - public async Task StripeSubscriptions(StripeSubscriptionListOptions options) - { - options = options ?? new StripeSubscriptionListOptions(); - options.Limit = 10; - options.Expand = new List() { "data.customer", "data.latest_invoice" }; - options.SelectAll = false; - - var subscriptions = await _stripeAdapter.SubscriptionListAsync(options); - - options.StartingAfter = subscriptions.LastOrDefault()?.Id; - options.EndingBefore = await StripeSubscriptionsGetHasPreviousPage(subscriptions, options) ? - subscriptions.FirstOrDefault()?.Id : - null; - - var isProduction = _environment.IsProduction(); - var model = new StripeSubscriptionsModel() - { - Items = subscriptions.Select(s => new StripeSubscriptionRowModel(s)).ToList(), - Prices = (await _stripeAdapter.PriceListAsync(new Stripe.PriceListOptions() { Limit = 100 })).Data, - TestClocks = isProduction ? new List() : await _stripeAdapter.TestClockListAsync(), - Filter = options - }; - return View(model); - } - - [HttpPost] - [RequirePermission(Permission.Tools_ManageStripeSubscriptions)] - public async Task StripeSubscriptions([FromForm] StripeSubscriptionsModel model) - { - if (!ModelState.IsValid) - { - var isProduction = _environment.IsProduction(); - model.Prices = (await _stripeAdapter.PriceListAsync(new Stripe.PriceListOptions() { Limit = 100 })).Data; - model.TestClocks = isProduction ? new List() : await _stripeAdapter.TestClockListAsync(); - return View(model); - } - - if (model.Action == StripeSubscriptionsAction.Export || model.Action == StripeSubscriptionsAction.BulkCancel) - { - var subscriptions = model.Filter.SelectAll ? - await _stripeAdapter.SubscriptionListAsync(model.Filter) : - model.Items.Where(x => x.Selected).Select(x => x.Subscription); - - if (model.Action == StripeSubscriptionsAction.Export) - { - return StripeSubscriptionsExport(subscriptions); - } - - if (model.Action == StripeSubscriptionsAction.BulkCancel) - { - await StripeSubscriptionsCancel(subscriptions); - } - } - else - { - if (model.Action == StripeSubscriptionsAction.PreviousPage || model.Action == StripeSubscriptionsAction.Search) - { - model.Filter.StartingAfter = null; - } - - if (model.Action == StripeSubscriptionsAction.NextPage || model.Action == StripeSubscriptionsAction.Search) - { - if (!string.IsNullOrEmpty(model.Filter.StartingAfter)) - { - var subscription = await _stripeAdapter.SubscriptionGetAsync(model.Filter.StartingAfter); - if (subscription.Status == "canceled") - { - model.Filter.StartingAfter = null; - } - } - model.Filter.EndingBefore = null; - } - } - - - return RedirectToAction("StripeSubscriptions", model.Filter); - } - - // This requires a redundant API call to Stripe because of the way they handle pagination. - // The StartingBefore value has to be inferred from the list we get, and isn't supplied by Stripe. - private async Task StripeSubscriptionsGetHasPreviousPage(List subscriptions, StripeSubscriptionListOptions options) - { - var hasPreviousPage = false; - if (subscriptions.FirstOrDefault()?.Id != null) - { - var previousPageSearchOptions = new StripeSubscriptionListOptions() - { - EndingBefore = subscriptions.FirstOrDefault().Id, - Limit = 1, - Status = options.Status, - CurrentPeriodEndDate = options.CurrentPeriodEndDate, - CurrentPeriodEndRange = options.CurrentPeriodEndRange, - Price = options.Price - }; - hasPreviousPage = (await _stripeAdapter.SubscriptionListAsync(previousPageSearchOptions)).Count > 0; - } - return hasPreviousPage; - } - - private async Task StripeSubscriptionsCancel(IEnumerable subscriptions) - { - foreach (var s in subscriptions) - { - await _stripeAdapter.SubscriptionCancelAsync(s.Id); - if (s.LatestInvoice?.Status == "open") - { - await _stripeAdapter.InvoiceVoidInvoiceAsync(s.LatestInvoiceId); - } - } - } - - private FileResult StripeSubscriptionsExport(IEnumerable subscriptions) - { - var fieldsToExport = subscriptions.Select(s => new - { - StripeId = s.Id, - CustomerEmail = s.Customer?.Email, - SubscriptionStatus = s.Status, - InvoiceDueDate = s.CurrentPeriodEnd, - SubscriptionProducts = s.Items?.Data.Select(p => p.Plan.Id) - }); - - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - - var result = System.Text.Json.JsonSerializer.Serialize(fieldsToExport, options); - var bytes = Encoding.UTF8.GetBytes(result); - return File(bytes, "application/json", "StripeSubscriptionsSearch.json"); - } } diff --git a/src/Admin/Enums/Permissions.cs b/src/Admin/Enums/Permissions.cs index 14b255b2b6..34d975226e 100644 --- a/src/Admin/Enums/Permissions.cs +++ b/src/Admin/Enums/Permissions.cs @@ -52,8 +52,6 @@ public enum Permission Tools_PromoteProviderServiceUser, Tools_GenerateLicenseFile, Tools_ManageTaxRates, - Tools_ManageStripeSubscriptions, Tools_CreateEditTransaction, - Tools_ProcessStripeEvents, - Tools_MigrateProviders + Tools_ProcessStripeEvents } diff --git a/src/Admin/Jobs/AliveJob.cs b/src/Admin/Jobs/AliveJob.cs index b97d597e58..d62f4cc2cc 100644 --- a/src/Admin/Jobs/AliveJob.cs +++ b/src/Admin/Jobs/AliveJob.cs @@ -22,7 +22,7 @@ public class AliveJob : BaseJob { _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: Keep alive"); var response = await _httpClient.GetAsync(_globalSettings.BaseServiceUri.Admin); - _logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: Keep alive, " + + _logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: Keep alive, {StatusCode}", response.StatusCode); } } diff --git a/src/Admin/Models/StripeSubscriptionsModel.cs b/src/Admin/Models/StripeSubscriptionsModel.cs deleted file mode 100644 index 36e1f099e1..0000000000 --- a/src/Admin/Models/StripeSubscriptionsModel.cs +++ /dev/null @@ -1,45 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using System.ComponentModel.DataAnnotations; -using Bit.Core.Models.BitStripe; - -namespace Bit.Admin.Models; - -public class StripeSubscriptionRowModel -{ - public Stripe.Subscription Subscription { get; set; } - public bool Selected { get; set; } - - public StripeSubscriptionRowModel() { } - public StripeSubscriptionRowModel(Stripe.Subscription subscription) - { - Subscription = subscription; - } -} - -public enum StripeSubscriptionsAction -{ - Search, - PreviousPage, - NextPage, - Export, - BulkCancel -} - -public class StripeSubscriptionsModel : IValidatableObject -{ - public List Items { get; set; } - public StripeSubscriptionsAction Action { get; set; } = StripeSubscriptionsAction.Search; - public string Message { get; set; } - public List Prices { get; set; } - public List TestClocks { get; set; } - public StripeSubscriptionListOptions Filter { get; set; } = new StripeSubscriptionListOptions(); - public IEnumerable Validate(ValidationContext validationContext) - { - if (Action == StripeSubscriptionsAction.BulkCancel && Filter.Status != "unpaid") - { - yield return new ValidationResult("Bulk cancel is currently only supported for unpaid subscriptions"); - } - } -} diff --git a/src/Admin/Startup.cs b/src/Admin/Startup.cs index 5b34e13f6c..5ecbdc899c 100644 --- a/src/Admin/Startup.cs +++ b/src/Admin/Startup.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection.Extensions; using Bit.Admin.Services; using Bit.Core.Billing.Extensions; -using Bit.Core.Billing.Providers.Migration; #if !OSS using Bit.Commercial.Core.Utilities; @@ -92,7 +91,6 @@ public class Startup services.AddDistributedCache(globalSettings); services.AddBillingOperations(); services.AddHttpClient(); - services.AddProviderMigration(); #if OSS services.AddOosServices(); diff --git a/src/Admin/Utilities/RolePermissionMapping.cs b/src/Admin/Utilities/RolePermissionMapping.cs index b60cf895a1..6dddc4ffeb 100644 --- a/src/Admin/Utilities/RolePermissionMapping.cs +++ b/src/Admin/Utilities/RolePermissionMapping.cs @@ -52,8 +52,7 @@ public static class RolePermissionMapping Permission.Tools_PromoteAdmin, Permission.Tools_PromoteProviderServiceUser, Permission.Tools_GenerateLicenseFile, - Permission.Tools_ManageTaxRates, - Permission.Tools_ManageStripeSubscriptions + Permission.Tools_ManageTaxRates } }, { "admin", new List @@ -105,7 +104,6 @@ public static class RolePermissionMapping Permission.Tools_PromoteProviderServiceUser, Permission.Tools_GenerateLicenseFile, Permission.Tools_ManageTaxRates, - Permission.Tools_ManageStripeSubscriptions, Permission.Tools_CreateEditTransaction } }, @@ -180,10 +178,8 @@ public static class RolePermissionMapping Permission.Tools_ChargeBrainTreeCustomer, Permission.Tools_GenerateLicenseFile, Permission.Tools_ManageTaxRates, - Permission.Tools_ManageStripeSubscriptions, Permission.Tools_CreateEditTransaction, - Permission.Tools_ProcessStripeEvents, - Permission.Tools_MigrateProviders + Permission.Tools_ProcessStripeEvents } }, { "sales", new List diff --git a/src/Admin/Views/Shared/_Layout.cshtml b/src/Admin/Views/Shared/_Layout.cshtml index 1661a8bbc3..c13be428b4 100644 --- a/src/Admin/Views/Shared/_Layout.cshtml +++ b/src/Admin/Views/Shared/_Layout.cshtml @@ -13,12 +13,10 @@ var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin); var canPromoteProviderServiceUser = AccessControlService.UserHasPermission(Permission.Tools_PromoteProviderServiceUser); var canGenerateLicense = AccessControlService.UserHasPermission(Permission.Tools_GenerateLicenseFile); - var canManageStripeSubscriptions = AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions); var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents); - var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders); var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin || canPromoteProviderServiceUser || - canGenerateLicense || canManageStripeSubscriptions; + canGenerateLicense; } @@ -102,12 +100,6 @@ Generate License - } - @if (canManageStripeSubscriptions) - { - - Manage Stripe Subscriptions - } @if (canProcessStripeEvents) { @@ -115,12 +107,6 @@ Process Stripe Events } - @if (canMigrateProviders) - { - - Migrate Providers - - } } diff --git a/src/Admin/Views/Tools/StripeSubscriptions.cshtml b/src/Admin/Views/Tools/StripeSubscriptions.cshtml deleted file mode 100644 index d8c168b3b0..0000000000 --- a/src/Admin/Views/Tools/StripeSubscriptions.cshtml +++ /dev/null @@ -1,277 +0,0 @@ -@model StripeSubscriptionsModel -@{ - ViewData["Title"] = "Stripe Subscriptions"; -} - -@section Scripts { - -} - -

Manage Stripe Subscriptions

-@if (!string.IsNullOrWhiteSpace(Model.Message)) -{ -
-} -
-
-
-
- - -
-
- -
-
-
- - -
-
- - -
-
- @{ - var date = @Model.Filter.CurrentPeriodEndDate.HasValue ? @Model.Filter.CurrentPeriodEndDate.Value.ToString("yyyy-MM-dd") : string.Empty; - } - -
-
-
- - -
-
- - -
-
- -
-
-
- -
-
- All @Model.Items.Count subscriptions on this page are selected.
- - - All subscriptions for this search are selected. - - -
-
-
- - - - - - - - - - - - - @if (!Model.Items.Any()) - { - - - - } - else - { - @for (var i = 0; i < Model.Items.Count; i++) - { - - - - - - - - - } - } - -
-
- -
-
IdCustomer EmailStatusProduct TierCurrent Period End
No results to list.
- - @{ - var i0 = i; - } - - - - - - - - @for (var j = 0; j < Model.Items[i].Subscription.Items.Data.Count; j++) - { - var i1 = i; - var j1 = j; - - } -
- - @{ - var i2 = i; - } - -
-
- @Model.Items[i].Subscription.Id - - @Model.Items[i].Subscription.Customer?.Email - - @Model.Items[i].Subscription.Status - - @string.Join(",", Model.Items[i].Subscription.Items.Data.Select(product => product.Plan.Id).ToArray()) - - @Model.Items[i].Subscription.CurrentPeriodEnd.ToShortDateString() -
-
- -
diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs index b34765fb19..8006a85734 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs @@ -70,6 +70,7 @@ public class OrganizationResponseModel : ResponseModel UseRiskInsights = organization.UseRiskInsights; UseOrganizationDomains = organization.UseOrganizationDomains; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; + UseAutomaticUserConfirmation = organization.UseAutomaticUserConfirmation; } public Guid Id { get; set; } @@ -118,6 +119,7 @@ public class OrganizationResponseModel : ResponseModel public bool UseRiskInsights { get; set; } public bool UseOrganizationDomains { get; set; } public bool UseAdminSponsoredFamilies { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } } public class OrganizationSubscriptionResponseModel : OrganizationResponseModel diff --git a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs index fd2bfe06dc..5a8669bb52 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs @@ -87,6 +87,8 @@ public class ProfileOrganizationResponseModel : ResponseModel KeyConnectorUrl = ssoConfigData.KeyConnectorUrl; SsoMemberDecryptionType = ssoConfigData.MemberDecryptionType; } + + UseAutomaticUserConfirmation = organization.UseAutomaticUserConfirmation; } public Guid Id { get; set; } @@ -164,4 +166,5 @@ public class ProfileOrganizationResponseModel : ResponseModel public bool IsAdminInitiated { get; set; } public bool SsoEnabled { get; set; } public MemberDecryptionType? SsoMemberDecryptionType { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } } diff --git a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs index 24b6fed704..fcbb949757 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -52,5 +52,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo UseRiskInsights = organization.UseRiskInsights; UseOrganizationDomains = organization.UseOrganizationDomains; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; + UseAutomaticUserConfirmation = organization.UseAutomaticUserConfirmation; } } diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index 19165a5a1c..ecf49c18c8 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -18,6 +18,7 @@ using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.KeyManagement.Kdf; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Api.Response; using Bit.Core.Repositories; using Bit.Core.Services; @@ -40,6 +41,7 @@ public class AccountsController : Controller private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; private readonly IFeatureService _featureService; + private readonly IUserAccountKeysQuery _userAccountKeysQuery; private readonly ITwoFactorEmailService _twoFactorEmailService; private readonly IChangeKdfCommand _changeKdfCommand; @@ -53,6 +55,7 @@ public class AccountsController : Controller ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, IFeatureService featureService, + IUserAccountKeysQuery userAccountKeysQuery, ITwoFactorEmailService twoFactorEmailService, IChangeKdfCommand changeKdfCommand ) @@ -66,6 +69,7 @@ public class AccountsController : Controller _tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; _featureService = featureService; + _userAccountKeysQuery = userAccountKeysQuery; _twoFactorEmailService = twoFactorEmailService; _changeKdfCommand = changeKdfCommand; } @@ -332,7 +336,9 @@ public class AccountsController : Controller var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user); var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id); - var response = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, + var accountKeys = await _userAccountKeysQuery.Run(user); + + var response = new ProfileResponseModel(user, accountKeys, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, twoFactorEnabled, hasPremiumFromOrg, organizationIdsClaimingActiveUser); return response; @@ -364,8 +370,9 @@ public class AccountsController : Controller var twoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user); var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user); var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id); + var userAccountKeys = await _userAccountKeysQuery.Run(user); - var response = new ProfileResponseModel(user, null, null, null, twoFactorEnabled, hasPremiumFromOrg, organizationIdsClaimingActiveUser); + var response = new ProfileResponseModel(user, userAccountKeys, null, null, null, twoFactorEnabled, hasPremiumFromOrg, organizationIdsClaimingActiveUser); return response; } @@ -389,8 +396,9 @@ public class AccountsController : Controller var userTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user); var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user); var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id); + var accountKeys = await _userAccountKeysQuery.Run(user); - var response = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsClaimingActiveUser); + var response = new ProfileResponseModel(user, accountKeys, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsClaimingActiveUser); return response; } diff --git a/src/Api/Billing/Controllers/AccountsController.cs b/src/Api/Billing/Controllers/AccountsController.cs index 9411d454aa..9dbe4a5532 100644 --- a/src/Api/Billing/Controllers/AccountsController.cs +++ b/src/Api/Billing/Controllers/AccountsController.cs @@ -1,4 +1,5 @@ #nullable enable + using Bit.Api.Models.Request; using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Response; @@ -8,6 +9,7 @@ using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Business; using Bit.Core.Billing.Services; using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Business; using Bit.Core.Services; using Bit.Core.Settings; @@ -21,7 +23,8 @@ namespace Bit.Api.Billing.Controllers; [Authorize("Application")] public class AccountsController( IUserService userService, - ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) : Controller + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IUserAccountKeysQuery userAccountKeysQuery) : Controller { [HttpPost("premium")] public async Task PostPremiumAsync( @@ -58,8 +61,9 @@ public class AccountsController( var userTwoFactorEnabled = await twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user); var userHasPremiumFromOrganization = await userService.HasPremiumFromOrganization(user); var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id); + var accountKeys = await userAccountKeysQuery.Run(user); - var profile = new ProfileResponseModel(user, null, null, null, userTwoFactorEnabled, + var profile = new ProfileResponseModel(user, accountKeys, null, null, null, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsClaimingActiveUser); return new PaymentResponseModel { diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 1d6bf51661..6e4cacc155 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -38,9 +38,7 @@ public class OrganizationBillingController( return Error.NotFound(); } - var response = OrganizationMetadataResponse.From(metadata); - - return TypedResults.Ok(response); + return TypedResults.Ok(metadata); } [HttpGet("history")] diff --git a/src/Api/Billing/Controllers/ProviderBillingController.cs b/src/Api/Billing/Controllers/ProviderBillingController.cs index f7d0593812..006a7ce068 100644 --- a/src/Api/Billing/Controllers/ProviderBillingController.cs +++ b/src/Api/Billing/Controllers/ProviderBillingController.cs @@ -132,7 +132,7 @@ public class ProviderBillingController( } var subscription = await stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, - new SubscriptionGetOptions { Expand = ["customer.tax_ids", "test_clock"] }); + new SubscriptionGetOptions { Expand = ["customer.tax_ids", "discounts", "test_clock"] }); var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); diff --git a/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs b/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs index 2f825f2cb9..64ec068a5e 100644 --- a/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs +++ b/src/Api/Billing/Controllers/VNext/OrganizationBillingVNextController.cs @@ -4,6 +4,7 @@ using Bit.Api.Billing.Attributes; using Bit.Api.Billing.Models.Requests.Payment; using Bit.Api.Billing.Models.Requests.Subscriptions; using Bit.Api.Billing.Models.Requirements; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Commands; using Bit.Core.Billing.Organizations.Queries; @@ -25,6 +26,7 @@ public class OrganizationBillingVNextController( ICreateBitPayInvoiceForCreditCommand createBitPayInvoiceForCreditCommand, IGetBillingAddressQuery getBillingAddressQuery, IGetCreditQuery getCreditQuery, + IGetOrganizationMetadataQuery getOrganizationMetadataQuery, IGetOrganizationWarningsQuery getOrganizationWarningsQuery, IGetPaymentMethodQuery getPaymentMethodQuery, IRestartSubscriptionCommand restartSubscriptionCommand, @@ -113,6 +115,23 @@ public class OrganizationBillingVNextController( return Handle(result); } + [Authorize] + [HttpGet("metadata")] + [RequireFeature(FeatureFlagKeys.PM25379_UseNewOrganizationMetadataStructure)] + [InjectOrganization] + public async Task GetMetadataAsync( + [BindNever] Organization organization) + { + var metadata = await getOrganizationMetadataQuery.Run(organization); + + if (metadata == null) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(metadata); + } + [Authorize] [HttpGet("warnings")] [InjectOrganization] diff --git a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs deleted file mode 100644 index a13f267c3b..0000000000 --- a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Bit.Core.Billing.Organizations.Models; - -namespace Bit.Api.Billing.Models.Responses; - -public record OrganizationMetadataResponse( - bool IsEligibleForSelfHost, - bool IsManaged, - bool IsOnSecretsManagerStandalone, - bool IsSubscriptionUnpaid, - bool HasSubscription, - bool HasOpenInvoice, - bool IsSubscriptionCanceled, - DateTime? InvoiceDueDate, - DateTime? InvoiceCreatedDate, - DateTime? SubPeriodEndDate, - int OrganizationOccupiedSeats) -{ - public static OrganizationMetadataResponse From(OrganizationMetadata metadata) - => new( - metadata.IsEligibleForSelfHost, - metadata.IsManaged, - metadata.IsOnSecretsManagerStandalone, - metadata.IsSubscriptionUnpaid, - metadata.HasSubscription, - metadata.HasOpenInvoice, - metadata.IsSubscriptionCanceled, - metadata.InvoiceDueDate, - metadata.InvoiceCreatedDate, - metadata.SubPeriodEndDate, - metadata.OrganizationOccupiedSeats); -} diff --git a/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs b/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs index e5b868af9a..4b78127240 100644 --- a/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs +++ b/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Models; using Bit.Core.Billing.Providers.Models; using Bit.Core.Billing.Tax.Models; @@ -10,7 +11,7 @@ namespace Bit.Api.Billing.Models.Responses; public record ProviderSubscriptionResponse( string Status, - DateTime CurrentPeriodEndDate, + DateTime? CurrentPeriodEndDate, decimal? DiscountPercentage, string CollectionMethod, IEnumerable Plans, @@ -51,10 +52,12 @@ public record ProviderSubscriptionResponse( var accountCredit = Convert.ToDecimal(subscription.Customer?.Balance) * -1 / 100; + var discount = subscription.Customer?.Discount ?? subscription.Discounts?.FirstOrDefault(); + return new ProviderSubscriptionResponse( subscription.Status, - subscription.CurrentPeriodEnd, - subscription.Customer?.Discount?.Coupon?.PercentOff, + subscription.GetCurrentPeriodEnd(), + discount?.Coupon?.PercentOff, subscription.CollectionMethod, providerPlanResponses, accountCredit, diff --git a/src/Api/Controllers/UsersController.cs b/src/Api/Controllers/UsersController.cs deleted file mode 100644 index 4dfd047d37..0000000000 --- a/src/Api/Controllers/UsersController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Bit.Api.Models.Response; -using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Bit.Api.Controllers; - -[Route("users")] -[Authorize("Application")] -public class UsersController : Controller -{ - private readonly IUserRepository _userRepository; - - public UsersController( - IUserRepository userRepository) - { - _userRepository = userRepository; - } - - [HttpGet("{id}/public-key")] - public async Task Get(string id) - { - var guidId = new Guid(id); - var key = await _userRepository.GetPublicKeyAsync(guidId); - if (key == null) - { - throw new NotFoundException(); - } - - return new UserKeyResponseModel(guidId, key); - } -} diff --git a/src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs b/src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs index 9fc0e9a75a..7968970048 100644 --- a/src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs +++ b/src/Api/KeyManagement/Controllers/AccountsKeyManagementController.cs @@ -106,8 +106,7 @@ public class AccountsKeyManagementController : Controller { OldMasterKeyAuthenticationHash = model.OldMasterKeyAuthenticationHash, - UserKeyEncryptedAccountPrivateKey = model.AccountKeys.UserKeyEncryptedAccountPrivateKey, - AccountPublicKey = model.AccountKeys.AccountPublicKey, + AccountKeys = model.AccountKeys.ToAccountKeysData(), MasterPasswordUnlockData = model.AccountUnlockData.MasterPasswordUnlockData.ToUnlockData(), EmergencyAccesses = await _emergencyAccessValidator.ValidateAsync(user, model.AccountUnlockData.EmergencyAccessUnlockData), diff --git a/src/Api/KeyManagement/Controllers/UsersController.cs b/src/Api/KeyManagement/Controllers/UsersController.cs new file mode 100644 index 0000000000..cfd2f8ee29 --- /dev/null +++ b/src/Api/KeyManagement/Controllers/UsersController.cs @@ -0,0 +1,39 @@ +using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Models.Api.Response; +using Bit.Core.KeyManagement.Queries.Interfaces; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using UserKeyResponseModel = Bit.Api.Models.Response.UserKeyResponseModel; + + +namespace Bit.Api.KeyManagement.Controllers; + +[Route("users")] +[Authorize("Application")] +public class UsersController : Controller +{ + private readonly IUserRepository _userRepository; + private readonly IUserAccountKeysQuery _userAccountKeysQuery; + + public UsersController(IUserRepository userRepository, IUserAccountKeysQuery userAccountKeysQuery) + { + _userRepository = userRepository; + _userAccountKeysQuery = userAccountKeysQuery; + } + + [HttpGet("{id}/public-key")] + public async Task GetPublicKeyAsync([FromRoute] Guid id) + { + var key = await _userRepository.GetPublicKeyAsync(id) ?? throw new NotFoundException(); + return new UserKeyResponseModel(id, key); + } + + [HttpGet("{id}/keys")] + public async Task GetAccountKeysAsync([FromRoute] Guid id) + { + var user = await _userRepository.GetByIdAsync(id) ?? throw new NotFoundException(); + var accountKeys = await _userAccountKeysQuery.Run(user) ?? throw new NotFoundException("User account keys not found."); + return new PublicKeysResponseModel(accountKeys); + } +} diff --git a/src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs b/src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs index 7c7de4d210..b64e826911 100644 --- a/src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs +++ b/src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs @@ -1,4 +1,5 @@ -#nullable enable +using Bit.Core.KeyManagement.Models.Api.Request; +using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Utilities; namespace Bit.Api.KeyManagement.Models.Requests; @@ -7,4 +8,44 @@ public class AccountKeysRequestModel { [EncryptedString] public required string UserKeyEncryptedAccountPrivateKey { get; set; } public required string AccountPublicKey { get; set; } + + public PublicKeyEncryptionKeyPairRequestModel? PublicKeyEncryptionKeyPair { get; set; } + public SignatureKeyPairRequestModel? SignatureKeyPair { get; set; } + public SecurityStateModel? SecurityState { get; set; } + + public UserAccountKeysData ToAccountKeysData() + { + // This will be cleaned up, after a compatibility period, at which point PublicKeyEncryptionKeyPair and SignatureKeyPair will be required. + // TODO: https://bitwarden.atlassian.net/browse/PM-23751 + if (PublicKeyEncryptionKeyPair == null) + { + return new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData + ( + UserKeyEncryptedAccountPrivateKey, + AccountPublicKey + ), + }; + } + else + { + if (SignatureKeyPair == null || SecurityState == null) + { + return new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = PublicKeyEncryptionKeyPair.ToPublicKeyEncryptionKeyPairData(), + }; + } + else + { + return new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = PublicKeyEncryptionKeyPair.ToPublicKeyEncryptionKeyPairData(), + SignatureKeyPairData = SignatureKeyPair.ToSignatureKeyPairData(), + SecurityStateData = SecurityState.ToSecurityState() + }; + } + } + } } diff --git a/src/Api/KeyManagement/Models/Requests/KeyRegenerationRequestModel.cs b/src/Api/KeyManagement/Models/Requests/KeyRegenerationRequestModel.cs index 495d13cccd..767cfd3f9b 100644 --- a/src/Api/KeyManagement/Models/Requests/KeyRegenerationRequestModel.cs +++ b/src/Api/KeyManagement/Models/Requests/KeyRegenerationRequestModel.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Utilities; namespace Bit.Api.KeyManagement.Models.Requests; diff --git a/src/Api/KeyManagement/Models/Requests/PublicKeyEncryptionKeyPairRequestModel.cs b/src/Api/KeyManagement/Models/Requests/PublicKeyEncryptionKeyPairRequestModel.cs new file mode 100644 index 0000000000..24c1e6a946 --- /dev/null +++ b/src/Api/KeyManagement/Models/Requests/PublicKeyEncryptionKeyPairRequestModel.cs @@ -0,0 +1,20 @@ +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Utilities; + +namespace Bit.Api.KeyManagement.Models.Requests; + +public class PublicKeyEncryptionKeyPairRequestModel +{ + [EncryptedString] public required string WrappedPrivateKey { get; set; } + public required string PublicKey { get; set; } + public string? SignedPublicKey { get; set; } + + public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData() + { + return new PublicKeyEncryptionKeyPairData( + WrappedPrivateKey, + PublicKey, + SignedPublicKey + ); + } +} diff --git a/src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs b/src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs index b0b19e2bd3..02780b015a 100644 --- a/src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs +++ b/src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Bit.Api.KeyManagement.Models.Requests; diff --git a/src/Api/KeyManagement/Models/Requests/SignatureKeyPairRequestModel.cs b/src/Api/KeyManagement/Models/Requests/SignatureKeyPairRequestModel.cs new file mode 100644 index 0000000000..3cdb4f53f1 --- /dev/null +++ b/src/Api/KeyManagement/Models/Requests/SignatureKeyPairRequestModel.cs @@ -0,0 +1,28 @@ +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Utilities; + +namespace Bit.Api.KeyManagement.Models.Requests; + +public class SignatureKeyPairRequestModel +{ + public required string SignatureAlgorithm { get; set; } + [EncryptedString] public required string WrappedSigningKey { get; set; } + public required string VerifyingKey { get; set; } + + public SignatureKeyPairData ToSignatureKeyPairData() + { + if (SignatureAlgorithm != "ed25519") + { + throw new ArgumentException( + $"Unsupported signature algorithm: {SignatureAlgorithm}" + ); + } + var algorithm = Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519; + + return new SignatureKeyPairData( + algorithm, + WrappedSigningKey, + VerifyingKey + ); + } +} diff --git a/src/Api/KeyManagement/Models/Requests/UnlockDataRequestModel.cs b/src/Api/KeyManagement/Models/Requests/UnlockDataRequestModel.cs index 3af944110c..01e5dd7017 100644 --- a/src/Api/KeyManagement/Models/Requests/UnlockDataRequestModel.cs +++ b/src/Api/KeyManagement/Models/Requests/UnlockDataRequestModel.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Models.Request.WebAuthn; diff --git a/src/Api/KeyManagement/Models/Requests/UserDataRequestModel.cs b/src/Api/KeyManagement/Models/Requests/UserDataRequestModel.cs index f854d82bcc..df922fcda0 100644 --- a/src/Api/KeyManagement/Models/Requests/UserDataRequestModel.cs +++ b/src/Api/KeyManagement/Models/Requests/UserDataRequestModel.cs @@ -1,5 +1,4 @@ -#nullable enable -using Bit.Api.Tools.Models.Request; +using Bit.Api.Tools.Models.Request; using Bit.Api.Vault.Models.Request; namespace Bit.Api.KeyManagement.Models.Requests; diff --git a/src/Api/Models/Request/Organizations/OrganizationVerifyBankRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationVerifyBankRequestModel.cs deleted file mode 100644 index 71f6873800..0000000000 --- a/src/Api/Models/Request/Organizations/OrganizationVerifyBankRequestModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Api.Models.Request.Organizations; - -public class OrganizationVerifyBankRequestModel -{ - [Required] - [Range(1, 99)] - public int? Amount1 { get; set; } - [Required] - [Range(1, 99)] - public int? Amount2 { get; set; } -} diff --git a/src/Api/Models/Response/ProfileResponseModel.cs b/src/Api/Models/Response/ProfileResponseModel.cs index cbdfaf0f16..30ba05b6a6 100644 --- a/src/Api/Models/Response/ProfileResponseModel.cs +++ b/src/Api/Models/Response/ProfileResponseModel.cs @@ -5,6 +5,8 @@ using Bit.Api.AdminConsole.Models.Response; using Bit.Api.AdminConsole.Models.Response.Providers; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Api.Response; +using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Models.Api; using Bit.Core.Models.Data.Organizations.OrganizationUsers; @@ -13,6 +15,7 @@ namespace Bit.Api.Models.Response; public class ProfileResponseModel : ResponseModel { public ProfileResponseModel(User user, + UserAccountKeysData userAccountKeysData, IEnumerable organizationsUserDetails, IEnumerable providerUserDetails, IEnumerable providerUserOrganizationDetails, @@ -35,6 +38,7 @@ public class ProfileResponseModel : ResponseModel TwoFactorEnabled = twoFactorEnabled; Key = user.Key; PrivateKey = user.PrivateKey; + AccountKeys = userAccountKeysData != null ? new PrivateKeysResponseModel(userAccountKeysData) : null; SecurityStamp = user.SecurityStamp; ForcePasswordReset = user.ForcePasswordReset; UsesKeyConnector = user.UsesKeyConnector; @@ -60,7 +64,9 @@ public class ProfileResponseModel : ResponseModel public string Culture { get; set; } public bool TwoFactorEnabled { get; set; } public string Key { get; set; } + [Obsolete("Use AccountKeys instead.")] public string PrivateKey { get; set; } + public PrivateKeysResponseModel AccountKeys { get; set; } public string SecurityStamp { get; set; } public bool ForcePasswordReset { get; set; } public bool UsesKeyConnector { get; set; } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 5d9918d1d4..1519bb25c8 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -326,6 +326,6 @@ public class Startup } // Log startup - logger.LogInformation(Constants.BypassFiltersEventId, globalSettings.ProjectName + " started."); + logger.LogInformation(Constants.BypassFiltersEventId, "{Project} started.", globalSettings.ProjectName); } } diff --git a/src/Api/Tools/Controllers/SendsController.cs b/src/Api/Tools/Controllers/SendsController.cs index c02e9b0c20..c54a9b90c9 100644 --- a/src/Api/Tools/Controllers/SendsController.cs +++ b/src/Api/Tools/Controllers/SendsController.cs @@ -166,7 +166,7 @@ public class SendsController : Controller } catch (Exception e) { - _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}"); + _logger.LogError(e, "Uncaught exception occurred while handling event grid event: {Event}", JsonSerializer.Serialize(eventGridEvent)); return; } } diff --git a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs index 91079d5040..1caa7cf841 100644 --- a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs +++ b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs @@ -152,7 +152,7 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute else { var logger = context.HttpContext.RequestServices.GetRequiredService>(); - logger.LogError(0, exception, exception.Message); + logger.LogError(0, exception, "Unhandled exception"); errorMessage = "An unhandled server error has occurred."; context.HttpContext.Response.StatusCode = 500; } diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 06c88ad9bb..fe3069d8c7 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -754,6 +754,11 @@ public class CiphersController : Controller } } + if (cipher.ArchivedDate.HasValue) + { + throw new BadRequestException("Cannot move an archived item to an organization."); + } + ValidateClientVersionForFido2CredentialSupport(cipher); var original = cipher.Clone(); @@ -1263,6 +1268,11 @@ public class CiphersController : Controller _logger.LogError("Cipher was not encrypted for the current user. CipherId: {CipherId}, CurrentUser: {CurrentUserId}, EncryptedFor: {EncryptedFor}", cipher.Id, userId, cipher.EncryptedFor); throw new BadRequestException("Cipher was not encrypted for the current user. Please try again."); } + + if (cipher.ArchivedDate.HasValue) + { + throw new BadRequestException("Cannot move archived items to an organization."); + } } var shareCiphers = new List<(CipherDetails, DateTime?)>(); @@ -1275,6 +1285,11 @@ public class CiphersController : Controller ValidateClientVersionForFido2CredentialSupport(existingCipher); + if (existingCipher.ArchivedDate.HasValue) + { + throw new BadRequestException("Cannot move archived items to an organization."); + } + shareCiphers.Add((cipher.ToCipherDetails(existingCipher), cipher.LastKnownRevisionDate)); } @@ -1578,7 +1593,7 @@ public class CiphersController : Controller } catch (Exception e) { - _logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}"); + _logger.LogError(e, "Uncaught exception occurred while handling event grid event: {Event}", JsonSerializer.Serialize(eventGridEvent)); return; } } diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index 54f1b9e70b..6ac8d06ba0 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -11,6 +11,8 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; @@ -42,6 +44,7 @@ public class SyncController : Controller private readonly IFeatureService _featureService; private readonly IApplicationCacheService _applicationCacheService; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; + private readonly IUserAccountKeysQuery _userAccountKeysQuery; public SyncController( IUserService userService, @@ -57,7 +60,8 @@ public class SyncController : Controller ICurrentContext currentContext, IFeatureService featureService, IApplicationCacheService applicationCacheService, - ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IUserAccountKeysQuery userAccountKeysQuery) { _userService = userService; _folderRepository = folderRepository; @@ -73,6 +77,7 @@ public class SyncController : Controller _featureService = featureService; _applicationCacheService = applicationCacheService; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; + _userAccountKeysQuery = userAccountKeysQuery; } [HttpGet("")] @@ -116,7 +121,14 @@ public class SyncController : Controller var organizationAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); - var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities, + UserAccountKeysData userAccountKeys = null; + // JIT TDE users and some broken/old users may not have a private key. + if (!string.IsNullOrWhiteSpace(user.PrivateKey)) + { + userAccountKeys = await _userAccountKeysQuery.Run(user); + } + + var response = new SyncResponseModel(_globalSettings, user, userAccountKeys, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities, organizationIdsClaimingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends); return response; diff --git a/src/Api/Vault/Models/Response/SyncResponseModel.cs b/src/Api/Vault/Models/Response/SyncResponseModel.cs index e19defce51..1981ac834e 100644 --- a/src/Api/Vault/Models/Response/SyncResponseModel.cs +++ b/src/Api/Vault/Models/Response/SyncResponseModel.cs @@ -7,7 +7,8 @@ using Bit.Api.Tools.Models.Response; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Entities; -using Bit.Core.KeyManagement.Models.Response; +using Bit.Core.KeyManagement.Models.Api.Response; +using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations; @@ -24,6 +25,7 @@ public class SyncResponseModel() : ResponseModel("sync") public SyncResponseModel( GlobalSettings globalSettings, User user, + UserAccountKeysData userAccountKeysData, bool userTwoFactorEnabled, bool userHasPremiumFromOrganization, IDictionary organizationAbilities, @@ -40,7 +42,7 @@ public class SyncResponseModel() : ResponseModel("sync") IEnumerable sends) : this() { - Profile = new ProfileResponseModel(user, organizationUserDetails, providerUserDetails, + Profile = new ProfileResponseModel(user, userAccountKeysData, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationIdsClaimingingUser); Folders = folders.Select(f => new FolderResponseModel(f)); Ciphers = ciphers.Select(cipher => diff --git a/src/Billing/BillingSettings.cs b/src/Billing/BillingSettings.cs index 3dc3e3e808..fc38f8fe60 100644 --- a/src/Billing/BillingSettings.cs +++ b/src/Billing/BillingSettings.cs @@ -7,9 +7,7 @@ public class BillingSettings { public virtual string JobsKey { get; set; } public virtual string StripeWebhookKey { get; set; } - public virtual string StripeWebhookSecret { get; set; } - public virtual string StripeWebhookSecret20231016 { get; set; } - public virtual string StripeWebhookSecret20240620 { get; set; } + public virtual string StripeWebhookSecret20250827Basil { get; set; } public virtual string BitPayWebhookKey { get; set; } public virtual string AppleWebhookKey { get; set; } public virtual FreshDeskSettings FreshDesk { get; set; } = new FreshDeskSettings(); diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index b60e0c56e4..18f2198119 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -120,9 +120,7 @@ public class StripeController : Controller return deliveryContainer.ApiVersion switch { - "2024-06-20" => HandleVersionWith(_billingSettings.StripeWebhookSecret20240620), - "2023-10-16" => HandleVersionWith(_billingSettings.StripeWebhookSecret20231016), - "2022-08-01" => HandleVersionWith(_billingSettings.StripeWebhookSecret), + "2025-08-27.basil" => HandleVersionWith(_billingSettings.StripeWebhookSecret20250827Basil), _ => HandleDefault(deliveryContainer.ApiVersion) }; diff --git a/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs b/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs index 5bb098bec5..101b0e26b9 100644 --- a/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs +++ b/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs @@ -1,4 +1,5 @@ -using Event = Stripe.Event; +using Bit.Core.Billing.Constants; +using Event = Stripe.Event; namespace Bit.Billing.Services.Implementations; @@ -35,13 +36,13 @@ public class InvoiceCreatedHandler( if (usingPayPal && invoice is { AmountDue: > 0, - Paid: false, + Status: not StripeConstants.InvoiceStatus.Paid, CollectionMethod: "charge_automatically", BillingReason: "subscription_create" or "subscription_cycle" or "automatic_pending_invoice_item_invoice", - SubscriptionId: not null and not "" + Parent.SubscriptionDetails: not null }) { await stripeEventUtilityService.AttemptToPayInvoiceAsync(invoice); diff --git a/src/Billing/Services/Implementations/PaymentFailedHandler.cs b/src/Billing/Services/Implementations/PaymentFailedHandler.cs index acf6ca70c7..0da6d03e94 100644 --- a/src/Billing/Services/Implementations/PaymentFailedHandler.cs +++ b/src/Billing/Services/Implementations/PaymentFailedHandler.cs @@ -1,4 +1,5 @@ -using Stripe; +using Bit.Core.Billing.Constants; +using Stripe; using Event = Stripe.Event; namespace Bit.Billing.Services.Implementations; @@ -26,17 +27,20 @@ public class PaymentFailedHandler : IPaymentFailedHandler public async Task HandleAsync(Event parsedEvent) { var invoice = await _stripeEventService.GetInvoice(parsedEvent, true); - if (invoice.Paid || invoice.AttemptCount <= 1 || !ShouldAttemptToPayInvoice(invoice)) + if (invoice.Status == StripeConstants.InvoiceStatus.Paid || invoice.AttemptCount <= 1 || !ShouldAttemptToPayInvoice(invoice)) { return; } - var subscription = await _stripeFacade.GetSubscription(invoice.SubscriptionId); - // attempt count 4 = 11 days after initial failure - if (invoice.AttemptCount <= 3 || - !subscription.Items.Any(i => i.Price.Id is IStripeEventUtilityService.PremiumPlanId or IStripeEventUtilityService.PremiumPlanIdAppStore)) + if (invoice.Parent?.SubscriptionDetails != null) { - await _stripeEventUtilityService.AttemptToPayInvoiceAsync(invoice); + var subscription = await _stripeFacade.GetSubscription(invoice.Parent.SubscriptionDetails.SubscriptionId); + // attempt count 4 = 11 days after initial failure + if (invoice.AttemptCount <= 3 || + !subscription.Items.Any(i => i.Price.Id is IStripeEventUtilityService.PremiumPlanId or IStripeEventUtilityService.PremiumPlanIdAppStore)) + { + await _stripeEventUtilityService.AttemptToPayInvoiceAsync(invoice); + } } } @@ -44,9 +48,9 @@ public class PaymentFailedHandler : IPaymentFailedHandler invoice is { AmountDue: > 0, - Paid: false, + Status: not StripeConstants.InvoiceStatus.Paid, CollectionMethod: "charge_automatically", BillingReason: "subscription_cycle" or "automatic_pending_invoice_item_invoice", - SubscriptionId: not null + Parent.SubscriptionDetails: not null }; } diff --git a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs index a10fa4b3d6..443227f7bf 100644 --- a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs +++ b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs @@ -1,7 +1,9 @@ using Bit.Billing.Constants; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Pricing; using Bit.Core.Repositories; using Bit.Core.Services; @@ -29,12 +31,17 @@ public class PaymentSucceededHandler( public async Task HandleAsync(Event parsedEvent) { var invoice = await stripeEventService.GetInvoice(parsedEvent, true); - if (!invoice.Paid || invoice.BillingReason != "subscription_create") + if (invoice.Status != StripeConstants.InvoiceStatus.Paid || invoice.BillingReason != "subscription_create") { return; } - var subscription = await stripeFacade.GetSubscription(invoice.SubscriptionId); + if (invoice.Parent?.SubscriptionDetails == null) + { + return; + } + + var subscription = await stripeFacade.GetSubscription(invoice.Parent.SubscriptionDetails.SubscriptionId); if (subscription?.Status != StripeSubscriptionStatus.Active) { return; @@ -96,7 +103,7 @@ public class PaymentSucceededHandler( return; } - await organizationEnableCommand.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd); + await organizationEnableCommand.EnableAsync(organizationId.Value, subscription.GetCurrentPeriodEnd()); organization = await organizationRepository.GetByIdAsync(organization.Id); await pushNotificationAdapter.NotifyEnabledChangedAsync(organization!); } @@ -107,7 +114,7 @@ public class PaymentSucceededHandler( return; } - await userService.EnablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd); + await userService.EnablePremiumAsync(userId.Value, subscription.GetCurrentPeriodEnd()); } } } diff --git a/src/Billing/Services/Implementations/ProviderEventService.cs b/src/Billing/Services/Implementations/ProviderEventService.cs index 12716c5aa2..79c85cb48f 100644 --- a/src/Billing/Services/Implementations/ProviderEventService.cs +++ b/src/Billing/Services/Implementations/ProviderEventService.cs @@ -28,9 +28,14 @@ public class ProviderEventService( return; } - var invoice = await stripeEventService.GetInvoice(parsedEvent); + var invoice = await stripeEventService.GetInvoice(parsedEvent, true, ["discounts"]); - var metadata = (await stripeFacade.GetSubscription(invoice.SubscriptionId)).Metadata ?? new Dictionary(); + if (invoice.Parent is not { Type: "subscription_details" }) + { + return; + } + + var metadata = (await stripeFacade.GetSubscription(invoice.Parent.SubscriptionDetails.SubscriptionId)).Metadata ?? new Dictionary(); var hasProviderId = metadata.TryGetValue("providerId", out var providerId); @@ -68,7 +73,9 @@ public class ProviderEventService( var plan = await pricingClient.GetPlanOrThrow(organization.PlanType); - var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100; + var totalPercentOff = invoice.Discounts?.Sum(discount => discount?.Coupon?.PercentOff ?? 0) ?? 0; + + var discountedPercentage = (100 - totalPercentOff) / 100; var discountedSeatPrice = plan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage; @@ -96,7 +103,9 @@ public class ProviderEventService( var unassignedSeats = providerPlan.SeatMinimum - clientSeats ?? 0; - var discountedPercentage = (100 - (invoice.Discount?.Coupon?.PercentOff ?? 0)) / 100; + var totalPercentOff = invoice.Discounts?.Sum(discount => discount?.Coupon?.PercentOff ?? 0) ?? 0; + + var discountedPercentage = (100 - totalPercentOff) / 100; var discountedSeatPrice = plan.PasswordManager.ProviderPortalSeatPrice * discountedPercentage; diff --git a/src/Billing/Services/Implementations/StripeEventUtilityService.cs b/src/Billing/Services/Implementations/StripeEventUtilityService.cs index 4c96bf977d..49e562de56 100644 --- a/src/Billing/Services/Implementations/StripeEventUtilityService.cs +++ b/src/Billing/Services/Implementations/StripeEventUtilityService.cs @@ -2,6 +2,7 @@ #nullable disable using Bit.Billing.Constants; +using Bit.Core.Billing.Constants; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -87,25 +88,6 @@ public class StripeEventUtilityService : IStripeEventUtilityService /// public async Task<(Guid?, Guid?, Guid?)> GetEntityIdsFromChargeAsync(Charge charge) { - Guid? organizationId = null; - Guid? userId = null; - Guid? providerId = null; - - if (charge.InvoiceId != null) - { - var invoice = await _stripeFacade.GetInvoice(charge.InvoiceId); - if (invoice?.SubscriptionId != null) - { - var subscription = await _stripeFacade.GetSubscription(invoice.SubscriptionId); - (organizationId, userId, providerId) = GetIdsFromMetadata(subscription?.Metadata); - } - } - - if (organizationId.HasValue || userId.HasValue || providerId.HasValue) - { - return (organizationId, userId, providerId); - } - var subscriptions = await _stripeFacade.ListSubscriptions(new SubscriptionListOptions { Customer = charge.CustomerId @@ -118,7 +100,7 @@ public class StripeEventUtilityService : IStripeEventUtilityService continue; } - (organizationId, userId, providerId) = GetIdsFromMetadata(subscription.Metadata); + var (organizationId, userId, providerId) = GetIdsFromMetadata(subscription.Metadata); if (organizationId.HasValue || userId.HasValue || providerId.HasValue) { @@ -256,10 +238,10 @@ public class StripeEventUtilityService : IStripeEventUtilityService invoice is { AmountDue: > 0, - Paid: false, + Status: not StripeConstants.InvoiceStatus.Paid, CollectionMethod: "charge_automatically", BillingReason: "subscription_cycle" or "automatic_pending_invoice_item_invoice", - SubscriptionId: not null + Parent.SubscriptionDetails: not null }; private async Task AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice, Customer customer) @@ -272,7 +254,13 @@ public class StripeEventUtilityService : IStripeEventUtilityService return false; } - var subscription = await _stripeFacade.GetSubscription(invoice.SubscriptionId); + if (invoice.Parent?.SubscriptionDetails == null) + { + _logger.LogWarning("Invoice parent was not a subscription."); + return false; + } + + var subscription = await _stripeFacade.GetSubscription(invoice.Parent.SubscriptionDetails.SubscriptionId); var (organizationId, userId, providerId) = GetIdsFromMetadata(subscription?.Metadata); if (!organizationId.HasValue && !userId.HasValue && !providerId.HasValue) { diff --git a/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs b/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs index 465da86c3f..13adf9825d 100644 --- a/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs @@ -1,5 +1,6 @@ using Bit.Billing.Constants; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.Billing.Extensions; using Bit.Core.Services; using Event = Stripe.Event; namespace Bit.Billing.Services.Implementations; @@ -50,11 +51,11 @@ public class SubscriptionDeletedHandler : ISubscriptionDeletedHandler return; } - await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd); + await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.GetCurrentPeriodEnd()); } else if (userId.HasValue) { - await _userService.DisablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd); + await _userService.DisablePremiumAsync(userId.Value, subscription.GetCurrentPeriodEnd()); } } } diff --git a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs index 10630f78f4..81aeb460c2 100644 --- a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs @@ -5,6 +5,8 @@ using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; +using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Pricing; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; @@ -82,12 +84,14 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler var subscription = await _stripeEventService.GetSubscription(parsedEvent, true, ["customer", "discounts", "latest_invoice", "test_clock"]); var (organizationId, userId, providerId) = _stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata); + var currentPeriodEnd = subscription.GetCurrentPeriodEnd(); + switch (subscription.Status) { case StripeSubscriptionStatus.Unpaid or StripeSubscriptionStatus.IncompleteExpired when organizationId.HasValue: { - await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd); + await _organizationDisableCommand.DisableAsync(organizationId.Value, currentPeriodEnd); if (subscription.Status == StripeSubscriptionStatus.Unpaid && subscription.LatestInvoice is { BillingReason: "subscription_cycle" or "subscription_create" }) { @@ -114,7 +118,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler await VoidOpenInvoices(subscription.Id); } - await _userService.DisablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd); + await _userService.DisablePremiumAsync(userId.Value, currentPeriodEnd); break; } @@ -154,7 +158,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler { if (userId.HasValue) { - await _userService.EnablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd); + await _userService.EnablePremiumAsync(userId.Value, currentPeriodEnd); } break; } @@ -162,17 +166,17 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler if (organizationId.HasValue) { - await _organizationService.UpdateExpirationDateAsync(organizationId.Value, subscription.CurrentPeriodEnd); - if (_stripeEventUtilityService.IsSponsoredSubscription(subscription)) + await _organizationService.UpdateExpirationDateAsync(organizationId.Value, currentPeriodEnd); + if (_stripeEventUtilityService.IsSponsoredSubscription(subscription) && currentPeriodEnd.HasValue) { - await _organizationSponsorshipRenewCommand.UpdateExpirationDateAsync(organizationId.Value, subscription.CurrentPeriodEnd); + await _organizationSponsorshipRenewCommand.UpdateExpirationDateAsync(organizationId.Value, currentPeriodEnd.Value); } await RemovePasswordManagerCouponIfRemovingSecretsManagerTrialAsync(parsedEvent, subscription); } else if (userId.HasValue) { - await _userService.UpdatePremiumExpirationAsync(userId.Value, subscription.CurrentPeriodEnd); + await _userService.UpdatePremiumExpirationAsync(userId.Value, currentPeriodEnd); } } @@ -280,9 +284,8 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler ?.Coupon ?.Id == "sm-standalone"; - var subscriptionHasSecretsManagerTrial = subscription.Discount - ?.Coupon - ?.Id == "sm-standalone"; + var subscriptionHasSecretsManagerTrial = subscription.Discounts.Select(discount => discount.Coupon.Id) + .Contains(StripeConstants.CouponIDs.SecretsManagerStandalone); if (customerHasSecretsManagerTrial) { diff --git a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs index e5675f7c0a..4260d67dfa 100644 --- a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs +++ b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs @@ -36,17 +36,16 @@ public class UpcomingInvoiceHandler( { var invoice = await stripeEventService.GetInvoice(parsedEvent); - if (string.IsNullOrEmpty(invoice.SubscriptionId)) + var customer = + await stripeFacade.GetCustomer(invoice.CustomerId, new CustomerGetOptions { Expand = ["subscriptions", "tax", "tax_ids"] }); + + var subscription = customer.Subscriptions.FirstOrDefault(); + + if (subscription == null) { - logger.LogInformation("Received 'invoice.upcoming' Event with ID '{eventId}' that did not include a Subscription ID", parsedEvent.Id); return; } - var subscription = await stripeFacade.GetSubscription(invoice.SubscriptionId, new SubscriptionGetOptions - { - Expand = ["customer.tax", "customer.tax_ids"] - }); - var (organizationId, userId, providerId) = stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata); if (organizationId.HasValue) @@ -58,7 +57,7 @@ public class UpcomingInvoiceHandler( return; } - await AlignOrganizationTaxConcernsAsync(organization, subscription, parsedEvent.Id); + await AlignOrganizationTaxConcernsAsync(organization, subscription, customer, parsedEvent.Id); var plan = await pricingClient.GetPlanOrThrow(organization.PlanType); @@ -137,7 +136,7 @@ public class UpcomingInvoiceHandler( return; } - await AlignProviderTaxConcernsAsync(provider, subscription, parsedEvent.Id); + await AlignProviderTaxConcernsAsync(provider, subscription, customer, parsedEvent.Id); await SendProviderUpcomingInvoiceEmailsAsync(new List { provider.BillingEmail }, invoice, subscription, providerId.Value); } @@ -199,13 +198,14 @@ public class UpcomingInvoiceHandler( private async Task AlignOrganizationTaxConcernsAsync( Organization organization, Subscription subscription, + Customer customer, string eventId) { var nonUSBusinessUse = organization.PlanType.GetProductTier() != ProductTierType.Families && - subscription.Customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates; + customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates; - if (nonUSBusinessUse && subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse) + if (nonUSBusinessUse && customer.TaxExempt != StripeConstants.TaxExempt.Reverse) { try { @@ -246,10 +246,11 @@ public class UpcomingInvoiceHandler( private async Task AlignProviderTaxConcernsAsync( Provider provider, Subscription subscription, + Customer customer, string eventId) { - if (subscription.Customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates && - subscription.Customer.TaxExempt != StripeConstants.TaxExempt.Reverse) + if (customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates && + customer.TaxExempt != StripeConstants.TaxExempt.Reverse) { try { diff --git a/src/Billing/appsettings.json b/src/Billing/appsettings.json index 6c90c22686..a2d6acd0a1 100644 --- a/src/Billing/appsettings.json +++ b/src/Billing/appsettings.json @@ -57,9 +57,7 @@ "billingSettings": { "jobsKey": "SECRET", "stripeWebhookKey": "SECRET", - "stripeWebhookSecret": "SECRET", - "stripeWebhookSecret20231016": "SECRET", - "stripeWebhookSecret20240620": "SECRET", + "stripeWebhookSecret20250827Basil": "SECRET", "bitPayWebhookKey": "SECRET", "appleWebhookKey": "SECRET", "payPal": { @@ -87,6 +85,6 @@ "runSearch": "always", "realTime": true } - } + } } } diff --git a/src/Core/AdminConsole/Entities/Organization.cs b/src/Core/AdminConsole/Entities/Organization.cs index 7933990e74..4cbde4a61a 100644 --- a/src/Core/AdminConsole/Entities/Organization.cs +++ b/src/Core/AdminConsole/Entities/Organization.cs @@ -129,6 +129,11 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable /// public bool SyncSeats { get; set; } + /// + /// If set to true, user accounts created within the organization are automatically confirmed without requiring additional verification steps. + /// + public bool UseAutomaticUserConfirmation { get; set; } + public void SetNewId() { if (Id == default(Guid)) diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs index ae91f204e3..3c02a4f50b 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs @@ -28,6 +28,7 @@ public class OrganizationAbility UseRiskInsights = organization.UseRiskInsights; UseOrganizationDomains = organization.UseOrganizationDomains; UseAdminSponsoredFamilies = organization.UseAdminSponsoredFamilies; + UseAutomaticUserConfirmation = organization.UseAutomaticUserConfirmation; } public Guid Id { get; set; } @@ -49,4 +50,5 @@ public class OrganizationAbility public bool UseRiskInsights { get; set; } public bool UseOrganizationDomains { get; set; } public bool UseAdminSponsoredFamilies { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs index b7e573c4e6..04e481d340 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs @@ -66,4 +66,5 @@ public class OrganizationUserOrganizationDetails public bool UseOrganizationDomains { get; set; } public bool UseAdminSponsoredFamilies { get; set; } public bool? IsAdminInitiated { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs index 04281d098e..7d68f685b8 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -51,4 +51,5 @@ public class ProviderUserOrganizationDetails public bool UseOrganizationDomains { get; set; } public bool UseAdminSponsoredFamilies { get; set; } public ProviderType ProviderType { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs b/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs index af966a6e16..9c699a61cb 100644 --- a/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs +++ b/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs @@ -89,7 +89,7 @@ public class UpdateOrganizationAuthRequestCommand : IUpdateOrganizationAuthReque AuthRequestExpiresAfter = _globalSettings.PasswordlessAuth.AdminRequestExpiration } ); - processor.Process((Exception e) => _logger.LogError(e.Message)); + processor.Process((Exception e) => _logger.LogError("Error processing organization auth request: {Message}", e.Message)); await processor.Save((IEnumerable authRequests) => _authRequestRepository.UpdateManyAsync(authRequests)); await processor.SendPushNotifications((ar) => _pushNotificationService.PushAuthRequestResponseAsync(ar)); await processor.SendApprovalEmailsForProcessedRequests(SendApprovalEmail); @@ -114,7 +114,7 @@ public class UpdateOrganizationAuthRequestCommand : IUpdateOrganizationAuthReque // This should be impossible if (user == null) { - _logger.LogError($"User {authRequest.UserId} not found. Trusted device admin approval email not sent."); + _logger.LogError("User {UserId} not found. Trusted device admin approval email not sent.", authRequest.UserId); return; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs index 1a2b78fc8a..5d40cb211f 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs @@ -13,25 +13,11 @@ public class VNextSavePolicyCommand( IApplicationCacheService applicationCacheService, IEventService eventService, IPolicyRepository policyRepository, - IEnumerable policyValidationEventHandlers, + IEnumerable policyUpdateEventHandlers, TimeProvider timeProvider, IPolicyEventHandlerFactory policyEventHandlerFactory) : IVNextSavePolicyCommand { - private readonly IReadOnlyDictionary _policyValidationEvents = MapToDictionary(policyValidationEventHandlers); - - private static Dictionary MapToDictionary(IEnumerable policyValidationEventHandlers) - { - var policyValidationEventsDict = new Dictionary(); - foreach (var policyValidationEvent in policyValidationEventHandlers) - { - if (!policyValidationEventsDict.TryAdd(policyValidationEvent.Type, policyValidationEvent)) - { - throw new Exception($"Duplicate PolicyValidationEvent for {policyValidationEvent.Type} policy."); - } - } - return policyValidationEventsDict; - } public async Task SaveAsync(SavePolicyModel policyRequest) { @@ -112,32 +98,26 @@ public class VNextSavePolicyCommand( Policy? currentPolicy, Dictionary savedPoliciesDict) { - var result = policyEventHandlerFactory.GetHandler(policyUpdateRequest.Type); + var isCurrentlyEnabled = currentPolicy?.Enabled == true; + var isBeingEnabled = policyUpdateRequest.Enabled && !isCurrentlyEnabled; + var isBeingDisabled = !policyUpdateRequest.Enabled && isCurrentlyEnabled; - result.Switch( - validator => - { - var isCurrentlyEnabled = currentPolicy?.Enabled == true; - - switch (policyUpdateRequest.Enabled) - { - case true when !isCurrentlyEnabled: - ValidateEnablingRequirements(validator, savedPoliciesDict); - return; - case false when isCurrentlyEnabled: - ValidateDisablingRequirements(validator, policyUpdateRequest.Type, savedPoliciesDict); - break; - } - }, - _ => { }); + if (isBeingEnabled) + { + ValidateEnablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); + } + else if (isBeingDisabled) + { + ValidateDisablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); + } } private void ValidateDisablingRequirements( - IEnforceDependentPoliciesEvent validator, PolicyType policyType, Dictionary savedPoliciesDict) { - var dependentPolicyTypes = _policyValidationEvents.Values + var dependentPolicyTypes = policyUpdateEventHandlers + .OfType() .Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType)) .Select(otherValidator => otherValidator.Type) .Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) && @@ -147,24 +127,31 @@ public class VNextSavePolicyCommand( switch (dependentPolicyTypes) { case { Count: 1 }: - throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {validator.Type.GetName()} policy."); + throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {policyType.GetName()} policy."); case { Count: > 1 }: - throw new BadRequestException($"Turn off all of the policies that require the {validator.Type.GetName()} policy."); + throw new BadRequestException($"Turn off all of the policies that require the {policyType.GetName()} policy."); } } - private static void ValidateEnablingRequirements( - IEnforceDependentPoliciesEvent validator, + private void ValidateEnablingRequirements( + PolicyType policyType, Dictionary savedPoliciesDict) { - var missingRequiredPolicyTypes = validator.RequiredPolicies - .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) - .ToList(); + var result = policyEventHandlerFactory.GetHandler(policyType); - if (missingRequiredPolicyTypes.Count != 0) - { - throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {validator.Type.GetName()} policy."); - } + result.Switch( + validator => + { + var missingRequiredPolicyTypes = validator.RequiredPolicies + .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) + .ToList(); + + if (missingRequiredPolicyTypes.Count != 0) + { + throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {policyType.GetName()} policy."); + } + }, + _ => { /* Policy has no required dependencies */ }); } private async Task ExecutePreUpsertSideEffectAsync( diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index f35ff87424..c90a1512a2 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -22,8 +22,10 @@ public static class PolicyServiceCollectionExtensions services.AddPolicyValidators(); services.AddPolicyRequirements(); services.AddPolicySideEffects(); + services.AddPolicyUpdateEvents(); } + [Obsolete("Use AddPolicyUpdateEvents instead.")] private static void AddPolicyValidators(this IServiceCollection services) { services.AddScoped(); @@ -34,11 +36,23 @@ public static class PolicyServiceCollectionExtensions services.AddScoped(); } + [Obsolete("Use AddPolicyUpdateEvents instead.")] private static void AddPolicySideEffects(this IServiceCollection services) { services.AddScoped(); } + private static void AddPolicyUpdateEvents(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + } + private static void AddPolicyRequirements(this IServiceCollection services) { services.AddScoped, DisableSendPolicyRequirementFactory>(); diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs index 57db4962e3..52a7e3e880 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs @@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; @@ -12,11 +13,16 @@ public class FreeFamiliesForEnterprisePolicyValidator( IOrganizationSponsorshipRepository organizationSponsorshipRepository, IMailService mailService, IOrganizationRepository organizationRepository) - : IPolicyValidator + : IPolicyValidator, IOnPolicyPreUpdateEvent { public PolicyType Type => PolicyType.FreeFamiliesSponsorshipPolicy; public IEnumerable RequiredPolicies => []; + public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); + } + public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs index bfd4dcfe0d..796ed286d8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs @@ -3,10 +3,11 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator +public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent { public PolicyType Type => PolicyType.MaximumVaultTimeout; public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs index f4ef6021a7..0bee2a55af 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs @@ -1,24 +1,32 @@  using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Repositories; using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -/// -/// Please do not extend or expand this validator. We're currently in the process of refactoring our policy validator pattern. -/// This is a stop-gap solution for post-policy-save side effects, but it is not the long-term solution. -/// public class OrganizationDataOwnershipPolicyValidator( IPolicyRepository policyRepository, ICollectionRepository collectionRepository, IEnumerable> factories, IFeatureService featureService) - : OrganizationPolicyValidator(policyRepository, factories), IPostSavePolicySideEffect + : OrganizationPolicyValidator(policyRepository, factories), IPostSavePolicySideEffect, IOnPolicyPostUpdateEvent { + public PolicyType Type => PolicyType.OrganizationDataOwnership; + + public async Task ExecutePostUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy postUpsertedPolicyState, + Policy? previousPolicyState) + { + await ExecuteSideEffectsAsync(policyRequest, postUpsertedPolicyState, previousPolicyState); + } + public async Task ExecuteSideEffectsAsync( SavePolicyModel policyRequest, Policy postUpdatedPolicy, @@ -68,5 +76,4 @@ public class OrganizationDataOwnershipPolicyValidator( userOrgIds, defaultCollectionName); } - } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs index 2082d4305f..adc2a3865a 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs @@ -3,12 +3,13 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -public class RequireSsoPolicyValidator : IPolicyValidator +public class RequireSsoPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent { private readonly ISsoConfigRepository _ssoConfigRepository; @@ -20,6 +21,11 @@ public class RequireSsoPolicyValidator : IPolicyValidator public PolicyType Type => PolicyType.RequireSso; public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; + public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); + } + public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) { if (policyUpdate is not { Enabled: true }) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs index 1126c4b922..9033a38ad0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs @@ -4,12 +4,13 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -public class ResetPasswordPolicyValidator : IPolicyValidator +public class ResetPasswordPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent { private readonly ISsoConfigRepository _ssoConfigRepository; public PolicyType Type => PolicyType.ResetPassword; @@ -20,6 +21,11 @@ public class ResetPasswordPolicyValidator : IPolicyValidator _ssoConfigRepository = ssoConfigRepository; } + public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); + } + public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) { if (policyUpdate is not { Enabled: true } || diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs index 49467eaae4..c0378bf5f9 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs @@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; using Bit.Core.Context; @@ -17,7 +18,7 @@ using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -public class SingleOrgPolicyValidator : IPolicyValidator +public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IOnPolicyPreUpdateEvent { public PolicyType Type => PolicyType.SingleOrg; private const string OrganizationNotFoundErrorMessage = "Organization not found."; @@ -57,6 +58,16 @@ public class SingleOrgPolicyValidator : IPolicyValidator public IEnumerable RequiredPolicies => []; + public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); + } + + public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); + } + public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs index 5ce72df6c1..7f3ebcccfb 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs @@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Context; using Bit.Core.Enums; @@ -16,7 +17,7 @@ using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator +public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator, IOnPolicyPreUpdateEvent { private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IMailService _mailService; @@ -46,6 +47,11 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator _revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand; } + public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) + { + await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); + } + public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) diff --git a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs index 37a830c92e..b17de3c51d 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs @@ -87,4 +87,13 @@ public interface IOrganizationUserRepository : IRepository> GetManyDetailsByRoleAsync(Guid organizationId, OrganizationUserType role); Task CreateManyAsync(IEnumerable organizationUserCollection); + + /// + /// It will only confirm if the user is in the `Accepted` state. + /// + /// This is an idempotent operation. + /// + /// Accepted OrganizationUser to confirm + /// True, if the user was updated. False, if not performed. + Task ConfirmOrganizationUserAsync(OrganizationUser organizationUser); } diff --git a/src/Core/AdminConsole/Services/OrganizationFactory.cs b/src/Core/AdminConsole/Services/OrganizationFactory.cs index afb3931ec4..42d6e7c8d5 100644 --- a/src/Core/AdminConsole/Services/OrganizationFactory.cs +++ b/src/Core/AdminConsole/Services/OrganizationFactory.cs @@ -61,6 +61,7 @@ public static class OrganizationFactory claimsPrincipal.GetValue(OrganizationLicenseConstants.UseOrganizationDomains), UseAdminSponsoredFamilies = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseAdminSponsoredFamilies), + UseAutomaticUserConfirmation = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseAutomaticUserConfirmation), }; public static Organization Create( diff --git a/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs b/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs index bd8542e8bf..aa8a298200 100644 --- a/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs +++ b/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs @@ -1,5 +1,5 @@ using System.Text.Json.Serialization; -using Bit.Core.KeyManagement.Models.Response; +using Bit.Core.KeyManagement.Models.Api.Response; using Bit.Core.Models.Api; namespace Bit.Core.Auth.Models.Api.Response; diff --git a/src/Core/Billing/Extensions/InvoiceExtensions.cs b/src/Core/Billing/Extensions/InvoiceExtensions.cs index bb9f7588bf..d62959c09a 100644 --- a/src/Core/Billing/Extensions/InvoiceExtensions.cs +++ b/src/Core/Billing/Extensions/InvoiceExtensions.cs @@ -64,10 +64,12 @@ public static class InvoiceExtensions } } + var tax = invoice.TotalTaxes?.Sum(invoiceTotalTax => invoiceTotalTax.Amount) ?? 0; + // Add fallback tax from invoice-level tax if present and not already included - if (invoice.Tax.HasValue && invoice.Tax.Value > 0) + if (tax > 0) { - var taxAmount = invoice.Tax.Value / 100m; + var taxAmount = tax / 100m; items.Add($"1 × Tax (at ${taxAmount:F2} / month)"); } diff --git a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs index 7aec422a4b..d6593f5365 100644 --- a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs +++ b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ public static class ServiceCollectionExtensions services.AddPaymentOperations(); services.AddOrganizationLicenseCommandsQueries(); services.AddPremiumCommands(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Core/Billing/Extensions/SubscriptionExtensions.cs b/src/Core/Billing/Extensions/SubscriptionExtensions.cs new file mode 100644 index 0000000000..383bd32d53 --- /dev/null +++ b/src/Core/Billing/Extensions/SubscriptionExtensions.cs @@ -0,0 +1,25 @@ +using Stripe; + +namespace Bit.Core.Billing.Extensions; + +public static class SubscriptionExtensions +{ + /* + * For the time being, this is the simplest migration approach from v45 to v48 as + * we do not support multi-cadence subscriptions. Each subscription item should be on the + * same billing cycle. If this changes, we'll need a significantly more robust approach. + * + * Because we can't guarantee a subscription will have items, this has to be nullable. + */ + public static (DateTime? Start, DateTime? End)? GetCurrentPeriod(this Subscription subscription) + { + var item = subscription.Items?.FirstOrDefault(); + return item is null ? null : (item.CurrentPeriodStart, item.CurrentPeriodEnd); + } + + public static DateTime? GetCurrentPeriodStart(this Subscription subscription) => + subscription.Items?.FirstOrDefault()?.CurrentPeriodStart; + + public static DateTime? GetCurrentPeriodEnd(this Subscription subscription) => + subscription.Items?.FirstOrDefault()?.CurrentPeriodEnd; +} diff --git a/src/Core/Billing/Extensions/UpcomingInvoiceOptionsExtensions.cs b/src/Core/Billing/Extensions/UpcomingInvoiceOptionsExtensions.cs deleted file mode 100644 index d00b5b46a4..0000000000 --- a/src/Core/Billing/Extensions/UpcomingInvoiceOptionsExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Stripe; - -namespace Bit.Core.Billing.Extensions; - -public static class UpcomingInvoiceOptionsExtensions -{ - /// - /// Attempts to enable automatic tax for given upcoming invoice options. - /// - /// - /// The existing customer to which the upcoming invoice belongs. - /// The existing subscription to which the upcoming invoice belongs. - /// Returns true when successful, false when conditions are not met. - public static bool EnableAutomaticTax( - this UpcomingInvoiceOptions options, - Customer customer, - Subscription subscription) - { - if (subscription != null && subscription.AutomaticTax.Enabled) - { - return false; - } - - // We might only need to check the automatic tax status. - if (!customer.HasRecognizedTaxLocation() && string.IsNullOrWhiteSpace(customer.Address?.Country)) - { - return false; - } - - options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true }; - options.SubscriptionDefaultTaxRates = []; - - return true; - } -} diff --git a/src/Core/Billing/Licenses/LicenseConstants.cs b/src/Core/Billing/Licenses/LicenseConstants.cs index cdfac76614..79ac94be62 100644 --- a/src/Core/Billing/Licenses/LicenseConstants.cs +++ b/src/Core/Billing/Licenses/LicenseConstants.cs @@ -43,6 +43,7 @@ public static class OrganizationLicenseConstants public const string Trial = nameof(Trial); public const string UseAdminSponsoredFamilies = nameof(UseAdminSponsoredFamilies); public const string UseOrganizationDomains = nameof(UseOrganizationDomains); + public const string UseAutomaticUserConfirmation = nameof(UseAutomaticUserConfirmation); } public static class UserLicenseConstants diff --git a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs index 1e049d7f03..e9aadbe758 100644 --- a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs +++ b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs @@ -56,6 +56,7 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory SecretsManager != null; + public bool AutomaticUserConfirmation { get; init; } + public bool HasNonSeatBasedPasswordManagerPlan() => PasswordManager is { StripePlanId: not null and not "", StripeSeatPlanId: null or "" }; diff --git a/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs b/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs index 77bbe655c4..89d301c22a 100644 --- a/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs +++ b/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs @@ -75,7 +75,13 @@ public class PreviewOrganizationTaxCommand( Quantity = purchase.SecretsManager.Seats } ]); - options.Coupon = CouponIDs.SecretsManagerStandalone; + options.Discounts = + [ + new InvoiceDiscountOptions + { + Coupon = CouponIDs.SecretsManagerStandalone + } + ]; break; default: @@ -180,7 +186,10 @@ public class PreviewOrganizationTaxCommand( if (subscription.Customer.Discount != null) { - options.Coupon = subscription.Customer.Discount.Coupon.Id; + options.Discounts = + [ + new InvoiceDiscountOptions { Coupon = subscription.Customer.Discount.Coupon.Id } + ]; } var currentPlan = await pricingClient.GetPlanOrThrow(organization.PlanType); @@ -277,7 +286,10 @@ public class PreviewOrganizationTaxCommand( if (subscription.Customer.Discount != null) { - options.Coupon = subscription.Customer.Discount.Coupon.Id; + options.Discounts = + [ + new InvoiceDiscountOptions { Coupon = subscription.Customer.Discount.Coupon.Id } + ]; } var currentPlan = await pricingClient.GetPlanOrThrow(organization.PlanType); @@ -329,7 +341,7 @@ public class PreviewOrganizationTaxCommand( }); private static (decimal, decimal) GetAmounts(Invoice invoice) => ( - Convert.ToDecimal(invoice.Tax) / 100, + Convert.ToDecimal(invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount)) / 100, Convert.ToDecimal(invoice.Total) / 100); private static InvoiceCreatePreviewOptions GetBaseOptions( diff --git a/src/Core/Billing/Organizations/Models/OrganizationLicense.cs b/src/Core/Billing/Organizations/Models/OrganizationLicense.cs index 83789be2f3..7ccbacc938 100644 --- a/src/Core/Billing/Organizations/Models/OrganizationLicense.cs +++ b/src/Core/Billing/Organizations/Models/OrganizationLicense.cs @@ -153,6 +153,7 @@ public class OrganizationLicense : ILicense public LicenseType? LicenseType { get; set; } public bool UseOrganizationDomains { get; set; } public bool UseAdminSponsoredFamilies { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } public string Hash { get; set; } public string Signature { get; set; } public string Token { get; set; } @@ -226,7 +227,8 @@ public class OrganizationLicense : ILicense // any new fields added need to be added here so that they're ignored !p.Name.Equals(nameof(UseRiskInsights)) && !p.Name.Equals(nameof(UseAdminSponsoredFamilies)) && - !p.Name.Equals(nameof(UseOrganizationDomains))) + !p.Name.Equals(nameof(UseOrganizationDomains)) && + !p.Name.Equals(nameof(UseAutomaticUserConfirmation))) .OrderBy(p => p.Name) .Select(p => $"{p.Name}:{Core.Utilities.CoreHelpers.FormatLicenseSignatureValue(p.GetValue(this, null))}") .Aggregate((c, n) => $"{c}|{n}"); @@ -421,6 +423,7 @@ public class OrganizationLicense : ILicense var smServiceAccounts = claimsPrincipal.GetValue(nameof(SmServiceAccounts)); var useAdminSponsoredFamilies = claimsPrincipal.GetValue(nameof(UseAdminSponsoredFamilies)); var useOrganizationDomains = claimsPrincipal.GetValue(nameof(UseOrganizationDomains)); + var useAutomaticUserConfirmation = claimsPrincipal.GetValue(nameof(UseAutomaticUserConfirmation)); return issued <= DateTime.UtcNow && expires >= DateTime.UtcNow && @@ -450,7 +453,8 @@ public class OrganizationLicense : ILicense smSeats == organization.SmSeats && smServiceAccounts == organization.SmServiceAccounts && useAdminSponsoredFamilies == organization.UseAdminSponsoredFamilies && - useOrganizationDomains == organization.UseOrganizationDomains; + useOrganizationDomains == organization.UseOrganizationDomains && + useAutomaticUserConfirmation == organization.UseAutomaticUserConfirmation; } diff --git a/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs b/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs index 2bcd213dbf..fedd0ad78c 100644 --- a/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs +++ b/src/Core/Billing/Organizations/Models/OrganizationMetadata.cs @@ -1,28 +1,10 @@ namespace Bit.Core.Billing.Organizations.Models; public record OrganizationMetadata( - bool IsEligibleForSelfHost, - bool IsManaged, bool IsOnSecretsManagerStandalone, - bool IsSubscriptionUnpaid, - bool HasSubscription, - bool HasOpenInvoice, - bool IsSubscriptionCanceled, - DateTime? InvoiceDueDate, - DateTime? InvoiceCreatedDate, - DateTime? SubPeriodEndDate, int OrganizationOccupiedSeats) { public static OrganizationMetadata Default => new OrganizationMetadata( false, - false, - false, - false, - false, - false, - false, - null, - null, - null, 0); } diff --git a/src/Core/Billing/Organizations/Models/OrganizationSale.cs b/src/Core/Billing/Organizations/Models/OrganizationSale.cs index f1f3a636b7..a984d5fe71 100644 --- a/src/Core/Billing/Organizations/Models/OrganizationSale.cs +++ b/src/Core/Billing/Organizations/Models/OrganizationSale.cs @@ -9,7 +9,7 @@ namespace Bit.Core.Billing.Organizations.Models; public class OrganizationSale { - private OrganizationSale() { } + internal OrganizationSale() { } public void Deconstruct( out Organization organization, diff --git a/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs b/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs new file mode 100644 index 0000000000..63da0477a1 --- /dev/null +++ b/src/Core/Billing/Organizations/Queries/GetOrganizationMetadataQuery.cs @@ -0,0 +1,95 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Organizations.Models; +using Bit.Core.Billing.Pricing; +using Bit.Core.Billing.Services; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Stripe; + +namespace Bit.Core.Billing.Organizations.Queries; + +public interface IGetOrganizationMetadataQuery +{ + Task Run(Organization organization); +} + +public class GetOrganizationMetadataQuery( + IGlobalSettings globalSettings, + IOrganizationRepository organizationRepository, + IPricingClient pricingClient, + ISubscriberService subscriberService) : IGetOrganizationMetadataQuery +{ + public async Task Run(Organization organization) + { + if (organization == null) + { + return null; + } + + if (globalSettings.SelfHosted) + { + return OrganizationMetadata.Default; + } + + var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); + + if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) + { + return OrganizationMetadata.Default with + { + OrganizationOccupiedSeats = orgOccupiedSeats.Total + }; + } + + var customer = await subscriberService.GetCustomer(organization, + new CustomerGetOptions { Expand = ["discount.coupon.applies_to"] }); + + var subscription = await subscriberService.GetSubscription(organization); + + if (customer == null || subscription == null) + { + return OrganizationMetadata.Default with + { + OrganizationOccupiedSeats = orgOccupiedSeats.Total + }; + } + + var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription); + + return new OrganizationMetadata( + isOnSecretsManagerStandalone, + orgOccupiedSeats.Total); + } + + private async Task IsOnSecretsManagerStandalone( + Organization organization, + Customer? customer, + Subscription? subscription) + { + if (customer == null || subscription == null) + { + return false; + } + + var plan = await pricingClient.GetPlanOrThrow(organization.PlanType); + + if (!plan.SupportsSecretsManager) + { + return false; + } + + var hasCoupon = customer.Discount?.Coupon?.Id == StripeConstants.CouponIDs.SecretsManagerStandalone; + + if (!hasCoupon) + { + return false; + } + + var subscriptionProductIds = subscription.Items.Data.Select(item => item.Plan.ProductId); + + var couponAppliesTo = customer.Discount?.Coupon?.AppliesTo?.Products; + + return subscriptionProductIds.Intersect(couponAppliesTo ?? []).Any(); + } +} diff --git a/src/Core/Billing/Organizations/Queries/GetOrganizationWarningsQuery.cs b/src/Core/Billing/Organizations/Queries/GetOrganizationWarningsQuery.cs index 887a6badf5..01e520ea41 100644 --- a/src/Core/Billing/Organizations/Queries/GetOrganizationWarningsQuery.cs +++ b/src/Core/Billing/Organizations/Queries/GetOrganizationWarningsQuery.cs @@ -162,17 +162,23 @@ public class GetOrganizationWarningsQuery( if (subscription is { Status: SubscriptionStatus.Trialing or SubscriptionStatus.Active, - LatestInvoice: null or { Status: InvoiceStatus.Paid } - } && (subscription.CurrentPeriodEnd - now).TotalDays <= 14) + LatestInvoice: null or { Status: InvoiceStatus.Paid }, + Items.Data.Count: > 0 + }) { - return new ResellerRenewalWarning + var currentPeriodEnd = subscription.GetCurrentPeriodEnd(); + + if (currentPeriodEnd != null && (currentPeriodEnd.Value - now).TotalDays <= 14) { - Type = "upcoming", - Upcoming = new ResellerRenewalWarning.UpcomingRenewal + return new ResellerRenewalWarning { - RenewalDate = subscription.CurrentPeriodEnd - } - }; + Type = "upcoming", + Upcoming = new ResellerRenewalWarning.UpcomingRenewal + { + RenewalDate = currentPeriodEnd.Value + } + }; + } } if (subscription is diff --git a/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs b/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs index 36a618f799..2381bdda96 100644 --- a/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs +++ b/src/Core/Billing/Organizations/Services/OrganizationBillingService.cs @@ -45,19 +45,14 @@ public class OrganizationBillingService( ? await CreateCustomerAsync(organization, customerSetup, subscriptionSetup.PlanType) : await GetCustomerWhileEnsuringCorrectTaxExemptionAsync(organization, subscriptionSetup); - var subscription = await CreateSubscriptionAsync(organization, customer, subscriptionSetup); + var subscription = await CreateSubscriptionAsync(organization, customer, subscriptionSetup, customerSetup?.Coupon); if (subscription.Status is StripeConstants.SubscriptionStatus.Trialing or StripeConstants.SubscriptionStatus.Active) { organization.Enabled = true; - organization.ExpirationDate = subscription.CurrentPeriodEnd; + organization.ExpirationDate = subscription.GetCurrentPeriodEnd(); + await organizationRepository.ReplaceAsync(organization); } - - organization.Gateway = GatewayType.Stripe; - organization.GatewayCustomerId = customer.Id; - organization.GatewaySubscriptionId = subscription.Id; - - await organizationRepository.ReplaceAsync(organization); } public async Task GetMetadata(Guid organizationId) @@ -74,16 +69,12 @@ public class OrganizationBillingService( return OrganizationMetadata.Default; } - var isEligibleForSelfHost = await IsEligibleForSelfHostAsync(organization); - - var isManaged = organization.Status == OrganizationStatusType.Managed; var orgOccupiedSeats = await organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); + if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { return OrganizationMetadata.Default with { - IsEligibleForSelfHost = isEligibleForSelfHost, - IsManaged = isManaged, OrganizationOccupiedSeats = orgOccupiedSeats.Total }; } @@ -97,28 +88,14 @@ public class OrganizationBillingService( { return OrganizationMetadata.Default with { - IsEligibleForSelfHost = isEligibleForSelfHost, - IsManaged = isManaged + OrganizationOccupiedSeats = orgOccupiedSeats.Total }; } var isOnSecretsManagerStandalone = await IsOnSecretsManagerStandalone(organization, customer, subscription); - var invoice = !string.IsNullOrEmpty(subscription.LatestInvoiceId) - ? await stripeAdapter.InvoiceGetAsync(subscription.LatestInvoiceId, new InvoiceGetOptions()) - : null; - return new OrganizationMetadata( - isEligibleForSelfHost, - isManaged, isOnSecretsManagerStandalone, - subscription.Status == StripeConstants.SubscriptionStatus.Unpaid, - true, - invoice?.Status == StripeConstants.InvoiceStatus.Open, - subscription.Status == StripeConstants.SubscriptionStatus.Canceled, - invoice?.DueDate, - invoice?.Created, - subscription.CurrentPeriodEnd, orgOccupiedSeats.Total); } @@ -210,7 +187,6 @@ public class OrganizationBillingService( var customerCreateOptions = new CustomerCreateOptions { - Coupon = customerSetup.Coupon, Description = organization.DisplayBusinessName(), Email = organization.BillingEmail, Expand = ["tax", "tax_ids"], @@ -273,8 +249,6 @@ public class OrganizationBillingService( ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately }; - - if (planType.GetProductTier() is not ProductTierType.Free and not ProductTierType.Families && customerSetup.TaxInformation.Country != Core.Constants.CountryAbbreviations.UnitedStates) { @@ -298,7 +272,7 @@ public class OrganizationBillingService( customerCreateOptions.TaxIdData = [ - new() { Type = taxIdType, Value = customerSetup.TaxInformation.TaxId } + new CustomerTaxIdDataOptions { Type = taxIdType, Value = customerSetup.TaxInformation.TaxId } ]; if (taxIdType == StripeConstants.TaxIdType.SpanishNIF) @@ -353,7 +327,13 @@ public class OrganizationBillingService( try { - return await stripeAdapter.CustomerCreateAsync(customerCreateOptions); + var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions); + + organization.Gateway = GatewayType.Stripe; + organization.GatewayCustomerId = customer.Id; + await organizationRepository.ReplaceAsync(organization); + + return customer; } catch (StripeException stripeException) when (stripeException.StripeError?.Code == StripeConstants.ErrorCodes.CustomerTaxLocationInvalid) @@ -400,7 +380,8 @@ public class OrganizationBillingService( private async Task CreateSubscriptionAsync( Organization organization, Customer customer, - SubscriptionSetup subscriptionSetup) + SubscriptionSetup subscriptionSetup, + string? coupon) { var plan = await pricingClient.GetPlanOrThrow(subscriptionSetup.PlanType); @@ -463,6 +444,7 @@ public class OrganizationBillingService( { CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, Customer = customer.Id, + Discounts = !string.IsNullOrEmpty(coupon) ? [new SubscriptionDiscountOptions { Coupon = coupon }] : null, Items = subscriptionItemOptionsList, Metadata = new Dictionary { @@ -478,8 +460,9 @@ public class OrganizationBillingService( var hasPaymentMethod = await hasPaymentMethodQuery.Run(organization); - // Only set trial_settings.end_behavior.missing_payment_method to "cancel" if there is no payment method - if (!hasPaymentMethod) + // Only set trial_settings.end_behavior.missing_payment_method to "cancel" + // if there is no payment method AND there's an actual trial period + if (!hasPaymentMethod && subscriptionCreateOptions.TrialPeriodDays > 0) { subscriptionCreateOptions.TrialSettings = new SubscriptionTrialSettingsOptions { @@ -494,7 +477,13 @@ public class OrganizationBillingService( { subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; } - return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); + + var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); + + organization.GatewaySubscriptionId = subscription.Id; + await organizationRepository.ReplaceAsync(organization); + + return subscription; } private async Task GetCustomerWhileEnsuringCorrectTaxExemptionAsync( @@ -536,16 +525,6 @@ public class OrganizationBillingService( return customer; } - private async Task IsEligibleForSelfHostAsync( - Organization organization) - { - var plans = await pricingClient.ListPlans(); - - var eligibleSelfHostPlans = plans.Where(plan => plan.HasSelfHost).Select(plan => plan.Type); - - return eligibleSelfHostPlans.Contains(organization.PlanType); - } - private async Task IsOnSecretsManagerStandalone( Organization organization, Customer? customer, diff --git a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs index 1227cdc034..c5fdc3287a 100644 --- a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs +++ b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs @@ -1,6 +1,7 @@ using Bit.Core.Billing.Caches; using Bit.Core.Billing.Commands; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Services; using Bit.Core.Entities; @@ -87,7 +88,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand( when subscription.Status == StripeConstants.SubscriptionStatus.Active: { user.Premium = true; - user.PremiumExpirationDate = subscription.CurrentPeriodEnd; + user.PremiumExpirationDate = subscription.GetCurrentPeriodEnd(); break; } } diff --git a/src/Core/Billing/Premium/Commands/PreviewPremiumTaxCommand.cs b/src/Core/Billing/Premium/Commands/PreviewPremiumTaxCommand.cs index a0b4fcabc2..9275bcf3d9 100644 --- a/src/Core/Billing/Premium/Commands/PreviewPremiumTaxCommand.cs +++ b/src/Core/Billing/Premium/Commands/PreviewPremiumTaxCommand.cs @@ -60,6 +60,6 @@ public class PreviewPremiumTaxCommand( }); private static (decimal, decimal) GetAmounts(Invoice invoice) => ( - Convert.ToDecimal(invoice.Tax) / 100, + Convert.ToDecimal(invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount)) / 100, Convert.ToDecimal(invoice.Total) / 100); } diff --git a/src/Core/Billing/Providers/Migration/Models/ClientMigrationTracker.cs b/src/Core/Billing/Providers/Migration/Models/ClientMigrationTracker.cs deleted file mode 100644 index 65fd7726f8..0000000000 --- a/src/Core/Billing/Providers/Migration/Models/ClientMigrationTracker.cs +++ /dev/null @@ -1,26 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -namespace Bit.Core.Billing.Providers.Migration.Models; - -public enum ClientMigrationProgress -{ - Started = 1, - MigrationRecordCreated = 2, - SubscriptionEnded = 3, - Completed = 4, - - Reversing = 5, - ResetOrganization = 6, - RecreatedSubscription = 7, - RemovedMigrationRecord = 8, - Reversed = 9 -} - -public class ClientMigrationTracker -{ - public Guid ProviderId { get; set; } - public Guid OrganizationId { get; set; } - public string OrganizationName { get; set; } - public ClientMigrationProgress Progress { get; set; } = ClientMigrationProgress.Started; -} diff --git a/src/Core/Billing/Providers/Migration/Models/ProviderMigrationResult.cs b/src/Core/Billing/Providers/Migration/Models/ProviderMigrationResult.cs deleted file mode 100644 index 78a2631999..0000000000 --- a/src/Core/Billing/Providers/Migration/Models/ProviderMigrationResult.cs +++ /dev/null @@ -1,48 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using Bit.Core.Billing.Providers.Entities; - -namespace Bit.Core.Billing.Providers.Migration.Models; - -public class ProviderMigrationResult -{ - public Guid ProviderId { get; set; } - public string ProviderName { get; set; } - public string Result { get; set; } - public List Clients { get; set; } -} - -public class ClientMigrationResult -{ - public Guid OrganizationId { get; set; } - public string OrganizationName { get; set; } - public string Result { get; set; } - public ClientPreviousState PreviousState { get; set; } -} - -public class ClientPreviousState -{ - public ClientPreviousState() { } - - public ClientPreviousState(ClientOrganizationMigrationRecord migrationRecord) - { - PlanType = migrationRecord.PlanType.ToString(); - Seats = migrationRecord.Seats; - MaxStorageGb = migrationRecord.MaxStorageGb; - GatewayCustomerId = migrationRecord.GatewayCustomerId; - GatewaySubscriptionId = migrationRecord.GatewaySubscriptionId; - ExpirationDate = migrationRecord.ExpirationDate; - MaxAutoscaleSeats = migrationRecord.MaxAutoscaleSeats; - Status = migrationRecord.Status.ToString(); - } - - public string PlanType { get; set; } - public int Seats { get; set; } - public short? MaxStorageGb { get; set; } - public string GatewayCustomerId { get; set; } = null!; - public string GatewaySubscriptionId { get; set; } = null!; - public DateTime? ExpirationDate { get; set; } - public int? MaxAutoscaleSeats { get; set; } - public string Status { get; set; } -} diff --git a/src/Core/Billing/Providers/Migration/Models/ProviderMigrationTracker.cs b/src/Core/Billing/Providers/Migration/Models/ProviderMigrationTracker.cs deleted file mode 100644 index ba39feab2d..0000000000 --- a/src/Core/Billing/Providers/Migration/Models/ProviderMigrationTracker.cs +++ /dev/null @@ -1,25 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -namespace Bit.Core.Billing.Providers.Migration.Models; - -public enum ProviderMigrationProgress -{ - Started = 1, - NoClients = 2, - ClientsMigrated = 3, - TeamsPlanConfigured = 4, - EnterprisePlanConfigured = 5, - CustomerSetup = 6, - SubscriptionSetup = 7, - CreditApplied = 8, - Completed = 9, -} - -public class ProviderMigrationTracker -{ - public Guid ProviderId { get; set; } - public string ProviderName { get; set; } - public List OrganizationIds { get; set; } - public ProviderMigrationProgress Progress { get; set; } = ProviderMigrationProgress.Started; -} diff --git a/src/Core/Billing/Providers/Migration/ServiceCollectionExtensions.cs b/src/Core/Billing/Providers/Migration/ServiceCollectionExtensions.cs deleted file mode 100644 index 1061c82888..0000000000 --- a/src/Core/Billing/Providers/Migration/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Bit.Core.Billing.Providers.Migration.Services; -using Bit.Core.Billing.Providers.Migration.Services.Implementations; -using Microsoft.Extensions.DependencyInjection; - -namespace Bit.Core.Billing.Providers.Migration; - -public static class ServiceCollectionExtensions -{ - public static void AddProviderMigration(this IServiceCollection services) - { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - } -} diff --git a/src/Core/Billing/Providers/Migration/Services/IMigrationTrackerCache.cs b/src/Core/Billing/Providers/Migration/Services/IMigrationTrackerCache.cs deleted file mode 100644 index 70649590df..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/IMigrationTrackerCache.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Entities.Provider; -using Bit.Core.Billing.Providers.Migration.Models; - -namespace Bit.Core.Billing.Providers.Migration.Services; - -public interface IMigrationTrackerCache -{ - Task StartTracker(Provider provider); - Task SetOrganizationIds(Guid providerId, IEnumerable organizationIds); - Task GetTracker(Guid providerId); - Task UpdateTrackingStatus(Guid providerId, ProviderMigrationProgress status); - - Task StartTracker(Guid providerId, Organization organization); - Task GetTracker(Guid providerId, Guid organizationId); - Task UpdateTrackingStatus(Guid providerId, Guid organizationId, ClientMigrationProgress status); -} diff --git a/src/Core/Billing/Providers/Migration/Services/IOrganizationMigrator.cs b/src/Core/Billing/Providers/Migration/Services/IOrganizationMigrator.cs deleted file mode 100644 index a0548277b4..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/IOrganizationMigrator.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Bit.Core.AdminConsole.Entities; - -namespace Bit.Core.Billing.Providers.Migration.Services; - -public interface IOrganizationMigrator -{ - Task Migrate(Guid providerId, Organization organization); -} diff --git a/src/Core/Billing/Providers/Migration/Services/IProviderMigrator.cs b/src/Core/Billing/Providers/Migration/Services/IProviderMigrator.cs deleted file mode 100644 index 328c2419f4..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/IProviderMigrator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Bit.Core.Billing.Providers.Migration.Models; - -namespace Bit.Core.Billing.Providers.Migration.Services; - -public interface IProviderMigrator -{ - Task Migrate(Guid providerId); - - Task GetResult(Guid providerId); -} diff --git a/src/Core/Billing/Providers/Migration/Services/Implementations/MigrationTrackerDistributedCache.cs b/src/Core/Billing/Providers/Migration/Services/Implementations/MigrationTrackerDistributedCache.cs deleted file mode 100644 index 1f38b0d111..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/Implementations/MigrationTrackerDistributedCache.cs +++ /dev/null @@ -1,110 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using System.Text.Json; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Entities.Provider; -using Bit.Core.Billing.Providers.Migration.Models; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.DependencyInjection; - -namespace Bit.Core.Billing.Providers.Migration.Services.Implementations; - -public class MigrationTrackerDistributedCache( - [FromKeyedServices("persistent")] - IDistributedCache distributedCache) : IMigrationTrackerCache -{ - public async Task StartTracker(Provider provider) => - await SetAsync(new ProviderMigrationTracker - { - ProviderId = provider.Id, - ProviderName = provider.Name - }); - - public async Task SetOrganizationIds(Guid providerId, IEnumerable organizationIds) - { - var tracker = await GetAsync(providerId); - - tracker.OrganizationIds = organizationIds.ToList(); - - await SetAsync(tracker); - } - - public Task GetTracker(Guid providerId) => GetAsync(providerId); - - public async Task UpdateTrackingStatus(Guid providerId, ProviderMigrationProgress status) - { - var tracker = await GetAsync(providerId); - - tracker.Progress = status; - - await SetAsync(tracker); - } - - public async Task StartTracker(Guid providerId, Organization organization) => - await SetAsync(new ClientMigrationTracker - { - ProviderId = providerId, - OrganizationId = organization.Id, - OrganizationName = organization.Name - }); - - public Task GetTracker(Guid providerId, Guid organizationId) => - GetAsync(providerId, organizationId); - - public async Task UpdateTrackingStatus(Guid providerId, Guid organizationId, ClientMigrationProgress status) - { - var tracker = await GetAsync(providerId, organizationId); - - tracker.Progress = status; - - await SetAsync(tracker); - } - - private static string GetProviderCacheKey(Guid providerId) => $"provider_{providerId}_migration"; - - private static string GetClientCacheKey(Guid providerId, Guid clientId) => - $"provider_{providerId}_client_{clientId}_migration"; - - private async Task GetAsync(Guid providerId) - { - var cacheKey = GetProviderCacheKey(providerId); - - var json = await distributedCache.GetStringAsync(cacheKey); - - return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize(json); - } - - private async Task GetAsync(Guid providerId, Guid organizationId) - { - var cacheKey = GetClientCacheKey(providerId, organizationId); - - var json = await distributedCache.GetStringAsync(cacheKey); - - return string.IsNullOrEmpty(json) ? null : JsonSerializer.Deserialize(json); - } - - private async Task SetAsync(ProviderMigrationTracker tracker) - { - var cacheKey = GetProviderCacheKey(tracker.ProviderId); - - var json = JsonSerializer.Serialize(tracker); - - await distributedCache.SetStringAsync(cacheKey, json, new DistributedCacheEntryOptions - { - SlidingExpiration = TimeSpan.FromMinutes(30) - }); - } - - private async Task SetAsync(ClientMigrationTracker tracker) - { - var cacheKey = GetClientCacheKey(tracker.ProviderId, tracker.OrganizationId); - - var json = JsonSerializer.Serialize(tracker); - - await distributedCache.SetStringAsync(cacheKey, json, new DistributedCacheEntryOptions - { - SlidingExpiration = TimeSpan.FromMinutes(30) - }); - } -} diff --git a/src/Core/Billing/Providers/Migration/Services/Implementations/OrganizationMigrator.cs b/src/Core/Billing/Providers/Migration/Services/Implementations/OrganizationMigrator.cs deleted file mode 100644 index 3de49838af..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/Implementations/OrganizationMigrator.cs +++ /dev/null @@ -1,331 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Billing.Constants; -using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Pricing; -using Bit.Core.Billing.Providers.Entities; -using Bit.Core.Billing.Providers.Migration.Models; -using Bit.Core.Billing.Providers.Repositories; -using Bit.Core.Enums; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Microsoft.Extensions.Logging; -using Stripe; -using Plan = Bit.Core.Models.StaticStore.Plan; - -namespace Bit.Core.Billing.Providers.Migration.Services.Implementations; - -public class OrganizationMigrator( - IClientOrganizationMigrationRecordRepository clientOrganizationMigrationRecordRepository, - ILogger logger, - IMigrationTrackerCache migrationTrackerCache, - IOrganizationRepository organizationRepository, - IPricingClient pricingClient, - IStripeAdapter stripeAdapter) : IOrganizationMigrator -{ - private const string _cancellationComment = "Cancelled as part of provider migration to Consolidated Billing"; - - public async Task Migrate(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Starting migration for organization ({OrganizationID})", organization.Id); - - await migrationTrackerCache.StartTracker(providerId, organization); - - await CreateMigrationRecordAsync(providerId, organization); - - await CancelSubscriptionAsync(providerId, organization); - - await UpdateOrganizationAsync(providerId, organization); - } - - #region Steps - - private async Task CreateMigrationRecordAsync(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Creating ClientOrganizationMigrationRecord for organization ({OrganizationID})", organization.Id); - - var migrationRecord = await clientOrganizationMigrationRecordRepository.GetByOrganizationId(organization.Id); - - if (migrationRecord != null) - { - logger.LogInformation( - "CB: ClientOrganizationMigrationRecord already exists for organization ({OrganizationID}), deleting record", - organization.Id); - - await clientOrganizationMigrationRecordRepository.DeleteAsync(migrationRecord); - } - - await clientOrganizationMigrationRecordRepository.CreateAsync(new ClientOrganizationMigrationRecord - { - OrganizationId = organization.Id, - ProviderId = providerId, - PlanType = organization.PlanType, - Seats = organization.Seats ?? 0, - MaxStorageGb = organization.MaxStorageGb, - GatewayCustomerId = organization.GatewayCustomerId!, - GatewaySubscriptionId = organization.GatewaySubscriptionId!, - ExpirationDate = organization.ExpirationDate, - MaxAutoscaleSeats = organization.MaxAutoscaleSeats, - Status = organization.Status - }); - - logger.LogInformation("CB: Created migration record for organization ({OrganizationID})", organization.Id); - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, - ClientMigrationProgress.MigrationRecordCreated); - } - - private async Task CancelSubscriptionAsync(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Cancelling subscription for organization ({OrganizationID})", organization.Id); - - var subscription = await stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId); - - if (subscription is - { - Status: - StripeConstants.SubscriptionStatus.Active or - StripeConstants.SubscriptionStatus.PastDue or - StripeConstants.SubscriptionStatus.Trialing - }) - { - await stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId, - new SubscriptionUpdateOptions { CancelAtPeriodEnd = false }); - - subscription = await stripeAdapter.SubscriptionCancelAsync(organization.GatewaySubscriptionId, - new SubscriptionCancelOptions - { - CancellationDetails = new SubscriptionCancellationDetailsOptions - { - Comment = _cancellationComment - }, - InvoiceNow = true, - Prorate = true, - Expand = ["latest_invoice", "test_clock"] - }); - - logger.LogInformation("CB: Cancelled subscription for organization ({OrganizationID})", organization.Id); - - var now = subscription.TestClock?.FrozenTime ?? DateTime.UtcNow; - - var trialing = subscription.TrialEnd.HasValue && subscription.TrialEnd.Value > now; - - if (!trialing && subscription is { Status: StripeConstants.SubscriptionStatus.Canceled, CancellationDetails.Comment: _cancellationComment }) - { - var latestInvoice = subscription.LatestInvoice; - - if (latestInvoice.Status == "draft") - { - await stripeAdapter.InvoiceFinalizeInvoiceAsync(latestInvoice.Id, - new InvoiceFinalizeOptions { AutoAdvance = true }); - - logger.LogInformation("CB: Finalized prorated invoice for organization ({OrganizationID})", organization.Id); - } - } - } - else - { - logger.LogInformation( - "CB: Did not need to cancel subscription for organization ({OrganizationID}) as it was inactive", - organization.Id); - } - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, - ClientMigrationProgress.SubscriptionEnded); - } - - private async Task UpdateOrganizationAsync(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Bringing organization ({OrganizationID}) under provider management", - organization.Id); - - var plan = await pricingClient.GetPlanOrThrow(organization.Plan.Contains("Teams") ? PlanType.TeamsMonthly : PlanType.EnterpriseMonthly); - - ResetOrganizationPlan(organization, plan); - organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb; - organization.GatewaySubscriptionId = null; - organization.ExpirationDate = null; - organization.MaxAutoscaleSeats = null; - organization.Status = OrganizationStatusType.Managed; - - await organizationRepository.ReplaceAsync(organization); - - logger.LogInformation("CB: Brought organization ({OrganizationID}) under provider management", - organization.Id); - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, - ClientMigrationProgress.Completed); - } - - #endregion - - #region Reverse - - private async Task RemoveMigrationRecordAsync(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Removing migration record for organization ({OrganizationID})", organization.Id); - - var migrationRecord = await clientOrganizationMigrationRecordRepository.GetByOrganizationId(organization.Id); - - if (migrationRecord != null) - { - await clientOrganizationMigrationRecordRepository.DeleteAsync(migrationRecord); - - logger.LogInformation( - "CB: Removed migration record for organization ({OrganizationID})", - organization.Id); - } - else - { - logger.LogInformation("CB: Did not remove migration record for organization ({OrganizationID}) as it does not exist", organization.Id); - } - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, ClientMigrationProgress.Reversed); - } - - private async Task RecreateSubscriptionAsync(Guid providerId, Organization organization) - { - logger.LogInformation("CB: Recreating subscription for organization ({OrganizationID})", organization.Id); - - if (!string.IsNullOrEmpty(organization.GatewaySubscriptionId)) - { - if (string.IsNullOrEmpty(organization.GatewayCustomerId)) - { - logger.LogError( - "CB: Cannot recreate subscription for organization ({OrganizationID}) as it does not have a Stripe customer", - organization.Id); - - throw new Exception(); - } - - var customer = await stripeAdapter.CustomerGetAsync(organization.GatewayCustomerId, - new CustomerGetOptions { Expand = ["default_source", "invoice_settings.default_payment_method"] }); - - var collectionMethod = - customer.DefaultSource != null || - customer.InvoiceSettings?.DefaultPaymentMethod != null || - customer.Metadata.ContainsKey(Utilities.BraintreeCustomerIdKey) - ? StripeConstants.CollectionMethod.ChargeAutomatically - : StripeConstants.CollectionMethod.SendInvoice; - - var plan = await pricingClient.GetPlanOrThrow(organization.PlanType); - - var items = new List - { - new () - { - Price = plan.PasswordManager.StripeSeatPlanId, - Quantity = organization.Seats - } - }; - - if (organization.MaxStorageGb.HasValue && plan.PasswordManager.BaseStorageGb.HasValue && organization.MaxStorageGb.Value > plan.PasswordManager.BaseStorageGb.Value) - { - var additionalStorage = organization.MaxStorageGb.Value - plan.PasswordManager.BaseStorageGb.Value; - - items.Add(new SubscriptionItemOptions - { - Price = plan.PasswordManager.StripeStoragePlanId, - Quantity = additionalStorage - }); - } - - var subscriptionCreateOptions = new SubscriptionCreateOptions - { - AutomaticTax = new SubscriptionAutomaticTaxOptions - { - Enabled = true - }, - Customer = customer.Id, - CollectionMethod = collectionMethod, - DaysUntilDue = collectionMethod == StripeConstants.CollectionMethod.SendInvoice ? 30 : null, - Items = items, - Metadata = new Dictionary - { - [organization.GatewayIdField()] = organization.Id.ToString() - }, - OffSession = true, - ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations, - TrialPeriodDays = plan.TrialPeriodDays - }; - - var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); - - organization.GatewaySubscriptionId = subscription.Id; - - await organizationRepository.ReplaceAsync(organization); - - logger.LogInformation("CB: Recreated subscription for organization ({OrganizationID})", organization.Id); - } - else - { - logger.LogInformation( - "CB: Did not recreate subscription for organization ({OrganizationID}) as it already exists", - organization.Id); - } - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, - ClientMigrationProgress.RecreatedSubscription); - } - - private async Task ReverseOrganizationUpdateAsync(Guid providerId, Organization organization) - { - var migrationRecord = await clientOrganizationMigrationRecordRepository.GetByOrganizationId(organization.Id); - - if (migrationRecord == null) - { - logger.LogError( - "CB: Cannot reverse migration for organization ({OrganizationID}) as it does not have a migration record", - organization.Id); - - throw new Exception(); - } - - var plan = await pricingClient.GetPlanOrThrow(migrationRecord.PlanType); - - ResetOrganizationPlan(organization, plan); - organization.MaxStorageGb = migrationRecord.MaxStorageGb; - organization.ExpirationDate = migrationRecord.ExpirationDate; - organization.MaxAutoscaleSeats = migrationRecord.MaxAutoscaleSeats; - organization.Status = migrationRecord.Status; - - await organizationRepository.ReplaceAsync(organization); - - logger.LogInformation("CB: Reversed organization ({OrganizationID}) updates", - organization.Id); - - await migrationTrackerCache.UpdateTrackingStatus(providerId, organization.Id, - ClientMigrationProgress.ResetOrganization); - } - - #endregion - - #region Shared - - private static void ResetOrganizationPlan(Organization organization, Plan plan) - { - organization.Plan = plan.Name; - organization.PlanType = plan.Type; - organization.MaxCollections = plan.PasswordManager.MaxCollections; - organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb; - organization.UsePolicies = plan.HasPolicies; - organization.UseSso = plan.HasSso; - organization.UseOrganizationDomains = plan.HasOrganizationDomains; - organization.UseGroups = plan.HasGroups; - organization.UseEvents = plan.HasEvents; - organization.UseDirectory = plan.HasDirectory; - organization.UseTotp = plan.HasTotp; - organization.Use2fa = plan.Has2fa; - organization.UseApi = plan.HasApi; - organization.UseResetPassword = plan.HasResetPassword; - organization.SelfHost = plan.HasSelfHost; - organization.UsersGetPremium = plan.UsersGetPremium; - organization.UseCustomPermissions = plan.HasCustomPermissions; - organization.UseScim = plan.HasScim; - organization.UseKeyConnector = plan.HasKeyConnector; - } - - #endregion -} diff --git a/src/Core/Billing/Providers/Migration/Services/Implementations/ProviderMigrator.cs b/src/Core/Billing/Providers/Migration/Services/Implementations/ProviderMigrator.cs deleted file mode 100644 index e155b427f1..0000000000 --- a/src/Core/Billing/Providers/Migration/Services/Implementations/ProviderMigrator.cs +++ /dev/null @@ -1,436 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Entities.Provider; -using Bit.Core.AdminConsole.Enums.Provider; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Billing.Constants; -using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models; -using Bit.Core.Billing.Providers.Entities; -using Bit.Core.Billing.Providers.Migration.Models; -using Bit.Core.Billing.Providers.Models; -using Bit.Core.Billing.Providers.Repositories; -using Bit.Core.Billing.Providers.Services; -using Bit.Core.Enums; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Microsoft.Extensions.Logging; -using Stripe; - -namespace Bit.Core.Billing.Providers.Migration.Services.Implementations; - -public class ProviderMigrator( - IClientOrganizationMigrationRecordRepository clientOrganizationMigrationRecordRepository, - IOrganizationMigrator organizationMigrator, - ILogger logger, - IMigrationTrackerCache migrationTrackerCache, - IOrganizationRepository organizationRepository, - IPaymentService paymentService, - IProviderBillingService providerBillingService, - IProviderOrganizationRepository providerOrganizationRepository, - IProviderRepository providerRepository, - IProviderPlanRepository providerPlanRepository, - IStripeAdapter stripeAdapter) : IProviderMigrator -{ - public async Task Migrate(Guid providerId) - { - var provider = await GetProviderAsync(providerId); - - if (provider == null) - { - return; - } - - logger.LogInformation("CB: Starting migration for provider ({ProviderID})", providerId); - - await migrationTrackerCache.StartTracker(provider); - - var organizations = await GetClientsAsync(provider.Id); - - if (organizations.Count == 0) - { - logger.LogInformation("CB: Skipping migration for provider ({ProviderID}) with no clients", providerId); - - await migrationTrackerCache.UpdateTrackingStatus(providerId, ProviderMigrationProgress.NoClients); - - return; - } - - await MigrateClientsAsync(providerId, organizations); - - await ConfigureTeamsPlanAsync(providerId); - - await ConfigureEnterprisePlanAsync(providerId); - - await SetupCustomerAsync(provider); - - await SetupSubscriptionAsync(provider); - - await ApplyCreditAsync(provider); - - await UpdateProviderAsync(provider); - } - - public async Task GetResult(Guid providerId) - { - var providerTracker = await migrationTrackerCache.GetTracker(providerId); - - if (providerTracker == null) - { - return null; - } - - if (providerTracker.Progress == ProviderMigrationProgress.NoClients) - { - return new ProviderMigrationResult - { - ProviderId = providerTracker.ProviderId, - ProviderName = providerTracker.ProviderName, - Result = providerTracker.Progress.ToString() - }; - } - - var clientTrackers = await Task.WhenAll(providerTracker.OrganizationIds.Select(organizationId => - migrationTrackerCache.GetTracker(providerId, organizationId))); - - var migrationRecordLookup = new Dictionary(); - - foreach (var clientTracker in clientTrackers) - { - var migrationRecord = - await clientOrganizationMigrationRecordRepository.GetByOrganizationId(clientTracker.OrganizationId); - - migrationRecordLookup.Add(clientTracker.OrganizationId, migrationRecord); - } - - return new ProviderMigrationResult - { - ProviderId = providerTracker.ProviderId, - ProviderName = providerTracker.ProviderName, - Result = providerTracker.Progress.ToString(), - Clients = clientTrackers.Select(tracker => - { - var foundMigrationRecord = migrationRecordLookup.TryGetValue(tracker.OrganizationId, out var migrationRecord); - return new ClientMigrationResult - { - OrganizationId = tracker.OrganizationId, - OrganizationName = tracker.OrganizationName, - Result = tracker.Progress.ToString(), - PreviousState = foundMigrationRecord ? new ClientPreviousState(migrationRecord) : null - }; - }).ToList(), - }; - } - - #region Steps - - private async Task MigrateClientsAsync(Guid providerId, List organizations) - { - logger.LogInformation("CB: Migrating clients for provider ({ProviderID})", providerId); - - var organizationIds = organizations.Select(organization => organization.Id); - - await migrationTrackerCache.SetOrganizationIds(providerId, organizationIds); - - foreach (var organization in organizations) - { - var tracker = await migrationTrackerCache.GetTracker(providerId, organization.Id); - - if (tracker is not { Progress: ClientMigrationProgress.Completed }) - { - await organizationMigrator.Migrate(providerId, organization); - } - } - - logger.LogInformation("CB: Migrated clients for provider ({ProviderID})", providerId); - - await migrationTrackerCache.UpdateTrackingStatus(providerId, - ProviderMigrationProgress.ClientsMigrated); - } - - private async Task ConfigureTeamsPlanAsync(Guid providerId) - { - logger.LogInformation("CB: Configuring Teams plan for provider ({ProviderID})", providerId); - - var organizations = await GetClientsAsync(providerId); - - var teamsSeats = organizations - .Where(IsTeams) - .Sum(client => client.Seats) ?? 0; - - var teamsProviderPlan = (await providerPlanRepository.GetByProviderId(providerId)) - .FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly); - - if (teamsProviderPlan == null) - { - await providerPlanRepository.CreateAsync(new ProviderPlan - { - ProviderId = providerId, - PlanType = PlanType.TeamsMonthly, - SeatMinimum = teamsSeats, - PurchasedSeats = 0, - AllocatedSeats = teamsSeats - }); - - logger.LogInformation("CB: Created Teams plan for provider ({ProviderID}) with a seat minimum of {Seats}", - providerId, teamsSeats); - } - else - { - logger.LogInformation("CB: Teams plan already exists for provider ({ProviderID}), updating seat minimum", providerId); - - teamsProviderPlan.SeatMinimum = teamsSeats; - teamsProviderPlan.AllocatedSeats = teamsSeats; - - await providerPlanRepository.ReplaceAsync(teamsProviderPlan); - - logger.LogInformation("CB: Updated Teams plan for provider ({ProviderID}) to seat minimum of {Seats}", - providerId, teamsProviderPlan.SeatMinimum); - } - - await migrationTrackerCache.UpdateTrackingStatus(providerId, ProviderMigrationProgress.TeamsPlanConfigured); - } - - private async Task ConfigureEnterprisePlanAsync(Guid providerId) - { - logger.LogInformation("CB: Configuring Enterprise plan for provider ({ProviderID})", providerId); - - var organizations = await GetClientsAsync(providerId); - - var enterpriseSeats = organizations - .Where(IsEnterprise) - .Sum(client => client.Seats) ?? 0; - - var enterpriseProviderPlan = (await providerPlanRepository.GetByProviderId(providerId)) - .FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly); - - if (enterpriseProviderPlan == null) - { - await providerPlanRepository.CreateAsync(new ProviderPlan - { - ProviderId = providerId, - PlanType = PlanType.EnterpriseMonthly, - SeatMinimum = enterpriseSeats, - PurchasedSeats = 0, - AllocatedSeats = enterpriseSeats - }); - - logger.LogInformation("CB: Created Enterprise plan for provider ({ProviderID}) with a seat minimum of {Seats}", - providerId, enterpriseSeats); - } - else - { - logger.LogInformation("CB: Enterprise plan already exists for provider ({ProviderID}), updating seat minimum", providerId); - - enterpriseProviderPlan.SeatMinimum = enterpriseSeats; - enterpriseProviderPlan.AllocatedSeats = enterpriseSeats; - - await providerPlanRepository.ReplaceAsync(enterpriseProviderPlan); - - logger.LogInformation("CB: Updated Enterprise plan for provider ({ProviderID}) to seat minimum of {Seats}", - providerId, enterpriseProviderPlan.SeatMinimum); - } - - await migrationTrackerCache.UpdateTrackingStatus(providerId, ProviderMigrationProgress.EnterprisePlanConfigured); - } - - private async Task SetupCustomerAsync(Provider provider) - { - if (string.IsNullOrEmpty(provider.GatewayCustomerId)) - { - var organizations = await GetClientsAsync(provider.Id); - - var sampleOrganization = organizations.FirstOrDefault(organization => !string.IsNullOrEmpty(organization.GatewayCustomerId)); - - if (sampleOrganization == null) - { - logger.LogInformation( - "CB: Could not find sample organization for provider ({ProviderID}) that has a Stripe customer", - provider.Id); - - return; - } - - var taxInfo = await paymentService.GetTaxInfoAsync(sampleOrganization); - - // Create dummy payment source for legacy migration - this migrator is deprecated and will be removed - var dummyPaymentSource = new TokenizedPaymentSource(PaymentMethodType.Card, "migration_dummy_token"); - - var customer = await providerBillingService.SetupCustomer(provider, null, null); - - await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions - { - Coupon = StripeConstants.CouponIDs.LegacyMSPDiscount - }); - - provider.GatewayCustomerId = customer.Id; - - await providerRepository.ReplaceAsync(provider); - - logger.LogInformation("CB: Setup Stripe customer for provider ({ProviderID})", provider.Id); - } - else - { - logger.LogInformation("CB: Stripe customer already exists for provider ({ProviderID})", provider.Id); - } - - await migrationTrackerCache.UpdateTrackingStatus(provider.Id, ProviderMigrationProgress.CustomerSetup); - } - - private async Task SetupSubscriptionAsync(Provider provider) - { - if (string.IsNullOrEmpty(provider.GatewaySubscriptionId)) - { - if (!string.IsNullOrEmpty(provider.GatewayCustomerId)) - { - var subscription = await providerBillingService.SetupSubscription(provider); - - provider.GatewaySubscriptionId = subscription.Id; - - await providerRepository.ReplaceAsync(provider); - - logger.LogInformation("CB: Setup Stripe subscription for provider ({ProviderID})", provider.Id); - } - else - { - logger.LogInformation( - "CB: Could not set up Stripe subscription for provider ({ProviderID}) with no Stripe customer", - provider.Id); - - return; - } - } - else - { - logger.LogInformation("CB: Stripe subscription already exists for provider ({ProviderID})", provider.Id); - - var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); - - var enterpriseSeatMinimum = providerPlans - .FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly)? - .SeatMinimum ?? 0; - - var teamsSeatMinimum = providerPlans - .FirstOrDefault(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly)? - .SeatMinimum ?? 0; - - var updateSeatMinimumsCommand = new UpdateProviderSeatMinimumsCommand( - provider, - [ - (Plan: PlanType.EnterpriseMonthly, SeatsMinimum: enterpriseSeatMinimum), - (Plan: PlanType.TeamsMonthly, SeatsMinimum: teamsSeatMinimum) - ]); - await providerBillingService.UpdateSeatMinimums(updateSeatMinimumsCommand); - - logger.LogInformation( - "CB: Updated Stripe subscription for provider ({ProviderID}) with current seat minimums", provider.Id); - } - - await migrationTrackerCache.UpdateTrackingStatus(provider.Id, ProviderMigrationProgress.SubscriptionSetup); - } - - private async Task ApplyCreditAsync(Provider provider) - { - var organizations = await GetClientsAsync(provider.Id); - - var organizationCustomers = - await Task.WhenAll(organizations.Select(organization => stripeAdapter.CustomerGetAsync(organization.GatewayCustomerId))); - - var organizationCancellationCredit = organizationCustomers.Sum(customer => customer.Balance); - - if (organizationCancellationCredit != 0) - { - await stripeAdapter.CustomerBalanceTransactionCreate(provider.GatewayCustomerId, - new CustomerBalanceTransactionCreateOptions - { - Amount = organizationCancellationCredit, - Currency = "USD", - Description = "Unused, prorated time for client organization subscriptions." - }); - } - - var migrationRecords = await Task.WhenAll(organizations.Select(organization => - clientOrganizationMigrationRecordRepository.GetByOrganizationId(organization.Id))); - - var legacyOrganizationMigrationRecords = migrationRecords.Where(migrationRecord => - migrationRecord.PlanType is - PlanType.EnterpriseAnnually2020 or - PlanType.TeamsAnnually2020); - - var legacyOrganizationCredit = legacyOrganizationMigrationRecords.Sum(migrationRecord => migrationRecord.Seats) * 12 * -100; - - if (legacyOrganizationCredit < 0) - { - await stripeAdapter.CustomerBalanceTransactionCreate(provider.GatewayCustomerId, - new CustomerBalanceTransactionCreateOptions - { - Amount = legacyOrganizationCredit, - Currency = "USD", - Description = "1 year rebate for legacy client organizations." - }); - } - - logger.LogInformation("CB: Applied {Credit} credit to provider ({ProviderID})", organizationCancellationCredit + legacyOrganizationCredit, provider.Id); - - await migrationTrackerCache.UpdateTrackingStatus(provider.Id, ProviderMigrationProgress.CreditApplied); - } - - private async Task UpdateProviderAsync(Provider provider) - { - provider.Status = ProviderStatusType.Billable; - - await providerRepository.ReplaceAsync(provider); - - logger.LogInformation("CB: Completed migration for provider ({ProviderID})", provider.Id); - - await migrationTrackerCache.UpdateTrackingStatus(provider.Id, ProviderMigrationProgress.Completed); - } - - #endregion - - #region Utilities - - private async Task> GetClientsAsync(Guid providerId) - { - var providerOrganizations = await providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId); - - return (await Task.WhenAll(providerOrganizations.Select(providerOrganization => - organizationRepository.GetByIdAsync(providerOrganization.OrganizationId)))) - .ToList(); - } - - private async Task GetProviderAsync(Guid providerId) - { - var provider = await providerRepository.GetByIdAsync(providerId); - - if (provider == null) - { - logger.LogWarning("CB: Cannot migrate provider ({ProviderID}) as it does not exist", providerId); - - return null; - } - - if (provider.Type != ProviderType.Msp) - { - logger.LogWarning("CB: Cannot migrate provider ({ProviderID}) as it is not an MSP", providerId); - - return null; - } - - if (provider.Status == ProviderStatusType.Created) - { - return provider; - } - - logger.LogWarning("CB: Cannot migrate provider ({ProviderID}) as it is not in the 'Created' state", providerId); - - return null; - } - - private static bool IsEnterprise(Organization organization) => organization.Plan.Contains("Enterprise"); - private static bool IsTeams(Organization organization) => organization.Plan.Contains("Teams"); - - #endregion -} diff --git a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs index 9db18278b6..e7e67c0a11 100644 --- a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs +++ b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs @@ -3,6 +3,7 @@ using Bit.Core.Billing.Caches; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Tax.Models; @@ -108,7 +109,7 @@ public class PremiumUserBillingService( when subscription.Status == StripeConstants.SubscriptionStatus.Active: { user.Premium = true; - user.PremiumExpirationDate = subscription.CurrentPeriodEnd; + user.PremiumExpirationDate = subscription.GetCurrentPeriodEnd(); break; } } diff --git a/src/Core/Billing/Subscriptions/Commands/RestartSubscriptionCommand.cs b/src/Core/Billing/Subscriptions/Commands/RestartSubscriptionCommand.cs index 351c75ace0..ee60597601 100644 --- a/src/Core/Billing/Subscriptions/Commands/RestartSubscriptionCommand.cs +++ b/src/Core/Billing/Subscriptions/Commands/RestartSubscriptionCommand.cs @@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Commands; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Repositories; @@ -65,7 +66,7 @@ public class RestartSubscriptionCommand( { organization.GatewaySubscriptionId = subscription.Id; organization.Enabled = true; - organization.ExpirationDate = subscription.CurrentPeriodEnd; + organization.ExpirationDate = subscription.GetCurrentPeriodEnd(); organization.RevisionDate = DateTime.UtcNow; await organizationRepository.ReplaceAsync(organization); break; @@ -82,7 +83,7 @@ public class RestartSubscriptionCommand( { user.GatewaySubscriptionId = subscription.Id; user.Premium = true; - user.PremiumExpirationDate = subscription.CurrentPeriodEnd; + user.PremiumExpirationDate = subscription.GetCurrentPeriodEnd(); user.RevisionDate = DateTime.UtcNow; await userRepository.ReplaceAsync(user); break; diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index b588ad05b5..54e8b07400 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -140,6 +140,7 @@ public static class FeatureFlagKeys public const string EventBasedOrganizationIntegrations = "event-based-organization-integrations"; public const string SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions"; public const string CreateDefaultLocation = "pm-19467-create-default-location"; + public const string AutomaticConfirmUsers = "pm-19934-auto-confirm-organization-users"; public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache"; /* Auth Team */ @@ -172,16 +173,17 @@ public static class FeatureFlagKeys public const string WindowsDesktopAutotype = "windows-desktop-autotype"; /* Billing Team */ - public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; public const string TrialPayment = "PM-8163-trial-payment"; public const string PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships"; public const string UsePricingService = "use-pricing-service"; public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates"; public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover"; public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings"; + public const string PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure"; public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog"; public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button"; public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog"; + public const string PremiumUpgradeNewDesign = "pm-24033-updat-premium-subscription-page"; /* Key Management Team */ public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair"; @@ -191,29 +193,26 @@ public static class FeatureFlagKeys public const string UserkeyRotationV2 = "userkey-rotation-v2"; public const string SSHKeyItemVaultItem = "ssh-key-vault-item"; public const string UserSdkForDecryption = "use-sdk-for-decryption"; + public const string EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation"; public const string PM17987_BlockType0 = "pm-17987-block-type-0"; public const string ForceUpdateKDFSettings = "pm-18021-force-update-kdf-settings"; public const string UnlockWithMasterPasswordUnlockData = "pm-23246-unlock-with-master-password-unlock-data"; public const string WindowsBiometricsV2 = "pm-25373-windows-biometrics-v2"; + public const string LinuxBiometricsV2 = "pm-26340-linux-biometrics-v2"; public const string NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change"; + public const string DisableType0Decryption = "pm-25174-disable-type-0-decryption"; + public const string ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component"; /* Mobile Team */ - public const string NativeCarouselFlow = "native-carousel-flow"; - public const string NativeCreateAccountFlow = "native-create-account-flow"; public const string AndroidImportLoginsFlow = "import-logins-flow"; - public const string AppReviewPrompt = "app-review-prompt"; public const string AndroidMutualTls = "mutual-tls"; public const string SingleTapPasskeyCreation = "single-tap-passkey-creation"; public const string SingleTapPasskeyAuthentication = "single-tap-passkey-authentication"; - public const string EnablePMAuthenticatorSync = "enable-pm-bwa-sync"; public const string PM3503_MobileAnonAddySelfHostAlias = "anon-addy-self-host-alias"; public const string PM3553_MobileSimpleLoginSelfHostAlias = "simple-login-self-host-alias"; - public const string EnablePMFlightRecorder = "enable-pm-flight-recorder"; public const string MobileErrorReporting = "mobile-error-reporting"; public const string AndroidChromeAutofill = "android-chrome-autofill"; public const string UserManagedPrivilegedApps = "pm-18970-user-managed-privileged-apps"; - public const string EnablePMPreloginSettings = "enable-pm-prelogin-settings"; - public const string AppIntents = "app-intents"; public const string SendAccess = "pm-19394-send-access-control"; public const string CxpImportMobile = "cxp-import-mobile"; public const string CxpExportMobile = "cxp-export-mobile"; @@ -226,12 +225,12 @@ public static class FeatureFlagKeys /* Tools Team */ public const string DesktopSendUIRefresh = "desktop-send-ui-refresh"; public const string UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators"; + public const string ChromiumImporterWithABE = "pm-25855-chromium-importer-abe"; /* Vault Team */ public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge"; public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form"; public const string CipherKeyEncryption = "cipher-key-encryption"; - public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms"; public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk"; public const string EndUserNotifications = "pm-10609-end-user-notifications"; public const string PhishingDetection = "phishing-detection"; diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 23cb885bd4..4c7d4ffc97 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -57,7 +57,7 @@ - + diff --git a/src/Core/Entities/User.cs b/src/Core/Entities/User.cs index 12c527ed78..fec9b80d8e 100644 --- a/src/Core/Entities/User.cs +++ b/src/Core/Entities/User.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Enums; +using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Utilities; using Microsoft.AspNetCore.Identity; @@ -21,6 +22,9 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac [MaxLength(256)] public string Email { get; set; } = null!; public bool EmailVerified { get; set; } + /// + /// The server-side master-password hash + /// [MaxLength(300)] public string? MasterPassword { get; set; } [MaxLength(50)] @@ -41,9 +45,30 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac /// organization membership. /// public DateTime AccountRevisionDate { get; set; } = DateTime.UtcNow; + /// + /// The master-password-sealed user key. + /// public string? Key { get; set; } + /// + /// The raw public key, without a signature from the user's signature key. + /// public string? PublicKey { get; set; } + /// + /// User key wrapped private key. + /// public string? PrivateKey { get; set; } + /// + /// The public key, signed by the user's signature key. + /// + public string? SignedPublicKey { get; set; } + /// + /// The security version is included in the security state, but needs COSE parsing + /// + public int? SecurityVersion { get; set; } + /// + /// The security state is a signed object attesting to the version of the user's account. + /// + public string? SecurityState { get; set; } public bool Premium { get; set; } public DateTime? PremiumExpirationDate { get; set; } public DateTime? RenewalReminderDate { get; set; } @@ -180,6 +205,12 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac return Premium; } + public int GetSecurityVersion() + { + // If no security version is set, it is version 1. The minimum initialized version is 2. + return SecurityVersion ?? 1; + } + /// /// Serializes the C# object to the User.TwoFactorProviders property in JSON format. /// @@ -243,4 +274,14 @@ public class User : ITableObject, IStorableSubscriber, IRevisable, ITwoFac { return MasterPassword != null; } + + public PublicKeyEncryptionKeyPairData GetPublicKeyEncryptionKeyPair() + { + if (string.IsNullOrWhiteSpace(PrivateKey) || string.IsNullOrWhiteSpace(PublicKey)) + { + throw new InvalidOperationException("User public key encryption key pair is not fully initialized."); + } + + return new PublicKeyEncryptionKeyPairData(PrivateKey, PublicKey, SignedPublicKey); + } } diff --git a/src/Core/Enums/PushNotificationLogOutReason.cs b/src/Core/Enums/PushNotificationLogOutReason.cs new file mode 100644 index 0000000000..a24f790305 --- /dev/null +++ b/src/Core/Enums/PushNotificationLogOutReason.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Enums; + +public enum PushNotificationLogOutReason : byte +{ + KdfChange = 0 +} diff --git a/src/Core/Jobs/BaseJobsHostedService.cs b/src/Core/Jobs/BaseJobsHostedService.cs index 3e7bce7e0f..8b74052f8f 100644 --- a/src/Core/Jobs/BaseJobsHostedService.cs +++ b/src/Core/Jobs/BaseJobsHostedService.cs @@ -107,7 +107,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable throw new Exception("Job failed to start after 10 retries."); } - _logger.LogWarning($"Exception while trying to schedule job: {job.FullName}, {e}"); + _logger.LogWarning(e, "Exception while trying to schedule job: {JobName}", job.FullName); var random = new Random(); await Task.Delay(random.Next(50, 250)); } @@ -125,7 +125,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable continue; } - _logger.LogInformation($"Deleting old job with key {key}"); + _logger.LogInformation("Deleting old job with key {Key}", key); await _scheduler.DeleteJob(key); } @@ -138,7 +138,7 @@ public abstract class BaseJobsHostedService : IHostedService, IDisposable continue; } - _logger.LogInformation($"Unscheduling old trigger with key {key}"); + _logger.LogInformation("Unscheduling old trigger with key {Key}", key); await _scheduler.UnscheduleJob(key); } } diff --git a/src/Core/KeyManagement/Entities/UserSignatureKeyPair.cs b/src/Core/KeyManagement/Entities/UserSignatureKeyPair.cs new file mode 100644 index 0000000000..dada9e0d7a --- /dev/null +++ b/src/Core/KeyManagement/Entities/UserSignatureKeyPair.cs @@ -0,0 +1,30 @@ +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Enums; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Utilities; + + +namespace Bit.Core.KeyManagement.Entities; + +public class UserSignatureKeyPair : ITableObject, IRevisable +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public SignatureAlgorithm SignatureAlgorithm { get; set; } + + public required string VerifyingKey { get; set; } + public required string SigningKey { get; set; } + + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime RevisionDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } + + public SignatureKeyPairData ToSignatureKeyPairData() + { + return new SignatureKeyPairData(SignatureAlgorithm, SigningKey, VerifyingKey); + } +} diff --git a/src/Core/KeyManagement/Enums/SignatureAlgorithm.cs b/src/Core/KeyManagement/Enums/SignatureAlgorithm.cs new file mode 100644 index 0000000000..9216c3f489 --- /dev/null +++ b/src/Core/KeyManagement/Enums/SignatureAlgorithm.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.KeyManagement.Enums; + +// +// Represents the algorithm / digital signature scheme used for a signature key pair. +// +public enum SignatureAlgorithm : byte +{ + Ed25519 = 0 +} diff --git a/src/Core/KeyManagement/Kdf/Implementations/ChangeKdfCommand.cs b/src/Core/KeyManagement/Kdf/Implementations/ChangeKdfCommand.cs index fe736f9ac6..83e47c4931 100644 --- a/src/Core/KeyManagement/Kdf/Implementations/ChangeKdfCommand.cs +++ b/src/Core/KeyManagement/Kdf/Implementations/ChangeKdfCommand.cs @@ -1,4 +1,5 @@ using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.Platform.Push; @@ -18,17 +19,22 @@ public class ChangeKdfCommand : IChangeKdfCommand private readonly IUserRepository _userRepository; private readonly IdentityErrorDescriber _identityErrorDescriber; private readonly ILogger _logger; + private readonly IFeatureService _featureService; - public ChangeKdfCommand(IUserService userService, IPushNotificationService pushService, IUserRepository userRepository, IdentityErrorDescriber describer, ILogger logger) + public ChangeKdfCommand(IUserService userService, IPushNotificationService pushService, + IUserRepository userRepository, IdentityErrorDescriber describer, ILogger logger, + IFeatureService featureService) { _userService = userService; _pushService = pushService; _userRepository = userRepository; _identityErrorDescriber = describer; _logger = logger; + _featureService = featureService; } - public async Task ChangeKdfAsync(User user, string masterPasswordAuthenticationHash, MasterPasswordAuthenticationData authenticationData, MasterPasswordUnlockData unlockData) + public async Task ChangeKdfAsync(User user, string masterPasswordAuthenticationHash, + MasterPasswordAuthenticationData authenticationData, MasterPasswordUnlockData unlockData) { ArgumentNullException.ThrowIfNull(user); if (!await _userService.CheckPasswordAsync(user, masterPasswordAuthenticationHash)) @@ -37,8 +43,8 @@ public class ChangeKdfCommand : IChangeKdfCommand } // Validate to prevent user account from becoming un-decryptable from invalid parameters - // - // Prevent a de-synced salt value from creating an un-decryptable unlock method + // + // Prevent a de-synced salt value from creating an un-decryptable unlock method authenticationData.ValidateSaltUnchangedForUser(user); unlockData.ValidateSaltUnchangedForUser(user); @@ -47,12 +53,15 @@ public class ChangeKdfCommand : IChangeKdfCommand { throw new BadRequestException("KDF settings must be equal for authentication and unlock."); } + var validationErrors = KdfSettingsValidator.Validate(unlockData.Kdf); if (validationErrors.Any()) { throw new BadRequestException("KDF settings are invalid."); } + var logoutOnKdfChange = !_featureService.IsEnabled(FeatureFlagKeys.NoLogoutOnKdfChange); + // Update the user with the new KDF settings // This updates the authentication data and unlock data for the user separately. Currently these still // use shared values for KDF settings and salt. @@ -68,7 +77,8 @@ public class ChangeKdfCommand : IChangeKdfCommand // This entire operation MUST be atomic to prevent a user from being locked out of their account. // Salt is ensured to be the same as unlock data, and the value stored in the account and not updated. // KDF is ensured to be the same as unlock data above and updated below. - var result = await _userService.UpdatePasswordHash(user, authenticationData.MasterPasswordAuthenticationHash); + var result = await _userService.UpdatePasswordHash(user, authenticationData.MasterPasswordAuthenticationHash, + refreshStamp: logoutOnKdfChange); if (!result.Succeeded) { _logger.LogWarning("Change KDF failed for user {userId}.", user.Id); @@ -88,7 +98,17 @@ public class ChangeKdfCommand : IChangeKdfCommand user.LastKdfChangeDate = now; await _userRepository.ReplaceAsync(user); - await _pushService.PushLogOutAsync(user.Id); + if (logoutOnKdfChange) + { + await _pushService.PushLogOutAsync(user.Id); + } + else + { + // Clients that support the new feature flag will ignore the logout when it matches the reason and the feature flag is enabled. + await _pushService.PushLogOutAsync(user.Id, reason: PushNotificationLogOutReason.KdfChange); + await _pushService.PushSyncSettingsAsync(user.Id); + } + return IdentityResult.Success; } } diff --git a/src/Core/KeyManagement/KeyManagementServiceCollectionExtensions.cs b/src/Core/KeyManagement/KeyManagementServiceCollectionExtensions.cs index e4ebdb4860..0e551c5d0e 100644 --- a/src/Core/KeyManagement/KeyManagementServiceCollectionExtensions.cs +++ b/src/Core/KeyManagement/KeyManagementServiceCollectionExtensions.cs @@ -2,6 +2,8 @@ using Bit.Core.KeyManagement.Commands.Interfaces; using Bit.Core.KeyManagement.Kdf; using Bit.Core.KeyManagement.Kdf.Implementations; +using Bit.Core.KeyManagement.Queries; +using Bit.Core.KeyManagement.Queries.Interfaces; using Microsoft.Extensions.DependencyInjection; namespace Bit.Core.KeyManagement; @@ -11,6 +13,7 @@ public static class KeyManagementServiceCollectionExtensions public static void AddKeyManagementServices(this IServiceCollection services) { services.AddKeyManagementCommands(); + services.AddKeyManagementQueries(); services.AddSendPasswordServices(); } @@ -19,4 +22,9 @@ public static class KeyManagementServiceCollectionExtensions services.AddScoped(); services.AddScoped(); } + + private static void AddKeyManagementQueries(this IServiceCollection services) + { + services.AddScoped(); + } } diff --git a/src/Core/KeyManagement/Models/Api/Request/SecurityStateModel.cs b/src/Core/KeyManagement/Models/Api/Request/SecurityStateModel.cs new file mode 100644 index 0000000000..1acb52146e --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Request/SecurityStateModel.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Bit.Core.KeyManagement.Models.Data; + +namespace Bit.Core.KeyManagement.Models.Api.Request; + +public class SecurityStateModel +{ + [StringLength(1000)] + [JsonPropertyName("securityState")] + public required string SecurityState { get; set; } + [JsonPropertyName("securityVersion")] + public required int SecurityVersion { get; set; } + + public SecurityStateData ToSecurityState() + { + return new SecurityStateData + { + SecurityState = SecurityState, + SecurityVersion = SecurityVersion + }; + } + + public static SecurityStateModel FromSecurityStateData(SecurityStateData data) + { + return new SecurityStateModel + { + SecurityState = data.SecurityState, + SecurityVersion = data.SecurityVersion + }; + } +} diff --git a/src/Core/KeyManagement/Models/Response/MasterPasswordUnlockResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/MasterPasswordUnlockResponseModel.cs similarity index 91% rename from src/Core/KeyManagement/Models/Response/MasterPasswordUnlockResponseModel.cs rename to src/Core/KeyManagement/Models/Api/Response/MasterPasswordUnlockResponseModel.cs index f7d5dee852..f54e88c596 100644 --- a/src/Core/KeyManagement/Models/Response/MasterPasswordUnlockResponseModel.cs +++ b/src/Core/KeyManagement/Models/Api/Response/MasterPasswordUnlockResponseModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Enums; using Bit.Core.Utilities; -namespace Bit.Core.KeyManagement.Models.Response; +namespace Bit.Core.KeyManagement.Models.Api.Response; public class MasterPasswordUnlockResponseModel { diff --git a/src/Core/KeyManagement/Models/Api/Response/PrivateKeysResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/PrivateKeysResponseModel.cs new file mode 100644 index 0000000000..bcee4c0ada --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Response/PrivateKeysResponseModel.cs @@ -0,0 +1,48 @@ +using System.Text.Json.Serialization; +using Bit.Core.KeyManagement.Models.Api.Request; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Models.Api; + +namespace Bit.Core.KeyManagement.Models.Api.Response; + + +/// +/// This response model is used to return the asymmetric encryption keys, +/// and signature keys of an entity. This includes the private keys of the key pairs, +/// (private key, signing key), and the public keys of the key pairs (unsigned public key, +/// signed public key, verification key). +/// +public class PrivateKeysResponseModel : ResponseModel +{ + // Not all accounts have signature keys, but all accounts have public encryption keys. + [JsonPropertyName("signatureKeyPair")] + public SignatureKeyPairResponseModel? SignatureKeyPair { get; set; } + + [JsonPropertyName("publicKeyEncryptionKeyPair")] + public required PublicKeyEncryptionKeyPairResponseModel PublicKeyEncryptionKeyPair { get; set; } + + [JsonPropertyName("securityState")] + public SecurityStateModel? SecurityState { get; set; } + + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public PrivateKeysResponseModel(UserAccountKeysData accountKeys) : base("privateKeys") + { + ArgumentNullException.ThrowIfNull(accountKeys); + PublicKeyEncryptionKeyPair = new PublicKeyEncryptionKeyPairResponseModel(accountKeys.PublicKeyEncryptionKeyPairData); + + if (accountKeys.SignatureKeyPairData != null && accountKeys.SecurityStateData != null) + { + SignatureKeyPair = new SignatureKeyPairResponseModel(accountKeys.SignatureKeyPairData); + SecurityState = SecurityStateModel.FromSecurityStateData(accountKeys.SecurityStateData!); + } + } + + [JsonConstructor] + public PrivateKeysResponseModel(SignatureKeyPairResponseModel? signatureKeyPair, PublicKeyEncryptionKeyPairResponseModel publicKeyEncryptionKeyPair, SecurityStateModel? securityState) + : base("privateKeys") + { + SignatureKeyPair = signatureKeyPair; + PublicKeyEncryptionKeyPair = publicKeyEncryptionKeyPair ?? throw new ArgumentNullException(nameof(publicKeyEncryptionKeyPair)); + SecurityState = securityState; + } +} diff --git a/src/Core/KeyManagement/Models/Api/Response/PublicKeyEncryptionKeyPairResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/PublicKeyEncryptionKeyPairResponseModel.cs new file mode 100644 index 0000000000..e5436b6131 --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Response/PublicKeyEncryptionKeyPairResponseModel.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Models.Api; + +namespace Bit.Core.KeyManagement.Models.Api.Response; + + +public class PublicKeyEncryptionKeyPairResponseModel : ResponseModel +{ + [JsonPropertyName("wrappedPrivateKey")] + public required string WrappedPrivateKey { get; set; } + [JsonPropertyName("publicKey")] + public required string PublicKey { get; set; } + [JsonPropertyName("signedPublicKey")] + public string? SignedPublicKey { get; set; } + + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public PublicKeyEncryptionKeyPairResponseModel(PublicKeyEncryptionKeyPairData keyPair) + : base("publicKeyEncryptionKeyPair") + { + WrappedPrivateKey = keyPair.WrappedPrivateKey; + PublicKey = keyPair.PublicKey; + SignedPublicKey = keyPair.SignedPublicKey; + } + + [JsonConstructor] + public PublicKeyEncryptionKeyPairResponseModel(string wrappedPrivateKey, string publicKey, string? signedPublicKey) + : base("publicKeyEncryptionKeyPair") + { + WrappedPrivateKey = wrappedPrivateKey ?? throw new ArgumentNullException(nameof(wrappedPrivateKey)); + PublicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey)); + SignedPublicKey = signedPublicKey; + } +} diff --git a/src/Core/KeyManagement/Models/Api/Response/PublicKeysResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/PublicKeysResponseModel.cs new file mode 100644 index 0000000000..b341a87e3e --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Response/PublicKeysResponseModel.cs @@ -0,0 +1,30 @@ +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Models.Api; + +namespace Bit.Core.KeyManagement.Models.Api.Response; + + +/// +/// This response model is used to return the public keys of a user, to any other registered user or entity on the server. +/// It can contain public keys (signature/encryption), and proofs between the two. It does not contain (encrypted) private keys. +/// +public class PublicKeysResponseModel : ResponseModel +{ + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public PublicKeysResponseModel(UserAccountKeysData accountKeys) + : base("publicKeys") + { + ArgumentNullException.ThrowIfNull(accountKeys); + PublicKey = accountKeys.PublicKeyEncryptionKeyPairData.PublicKey; + + if (accountKeys.SignatureKeyPairData != null) + { + SignedPublicKey = accountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; + VerifyingKey = accountKeys.SignatureKeyPairData.VerifyingKey; + } + } + + public string? VerifyingKey { get; set; } + public string? SignedPublicKey { get; set; } + public required string PublicKey { get; set; } +} diff --git a/src/Core/KeyManagement/Models/Api/Response/SignatureKeyPairResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/SignatureKeyPairResponseModel.cs new file mode 100644 index 0000000000..34d51f8bd4 --- /dev/null +++ b/src/Core/KeyManagement/Models/Api/Response/SignatureKeyPairResponseModel.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Models.Api; + +namespace Bit.Core.KeyManagement.Models.Api.Response; + + +public class SignatureKeyPairResponseModel : ResponseModel +{ + [JsonPropertyName("wrappedSigningKey")] + public required string WrappedSigningKey { get; set; } + [JsonPropertyName("verifyingKey")] + public required string VerifyingKey { get; set; } + + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public SignatureKeyPairResponseModel(SignatureKeyPairData signatureKeyPair) + : base("signatureKeyPair") + { + ArgumentNullException.ThrowIfNull(signatureKeyPair); + WrappedSigningKey = signatureKeyPair.WrappedSigningKey; + VerifyingKey = signatureKeyPair.VerifyingKey; + } + + + [JsonConstructor] + public SignatureKeyPairResponseModel(string wrappedSigningKey, string verifyingKey) + : base("signatureKeyPair") + { + WrappedSigningKey = wrappedSigningKey ?? throw new ArgumentNullException(nameof(wrappedSigningKey)); + VerifyingKey = verifyingKey ?? throw new ArgumentNullException(nameof(verifyingKey)); + } +} diff --git a/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs b/src/Core/KeyManagement/Models/Api/Response/UserDecryptionResponseModel.cs similarity index 82% rename from src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs rename to src/Core/KeyManagement/Models/Api/Response/UserDecryptionResponseModel.cs index a4d259a00a..536347cea9 100644 --- a/src/Core/KeyManagement/Models/Response/UserDecryptionResponseModel.cs +++ b/src/Core/KeyManagement/Models/Api/Response/UserDecryptionResponseModel.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.KeyManagement.Models.Response; +namespace Bit.Core.KeyManagement.Models.Api.Response; public class UserDecryptionResponseModel { diff --git a/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs b/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs index c0ae949a3f..1bc7006cef 100644 --- a/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs +++ b/src/Core/KeyManagement/Models/Data/MasterPasswordAuthenticationData.cs @@ -1,4 +1,5 @@ using Bit.Core.Entities; +using Bit.Core.Exceptions; namespace Bit.Core.KeyManagement.Models.Data; @@ -12,7 +13,7 @@ public class MasterPasswordAuthenticationData { if (user.GetMasterPasswordSalt() != Salt) { - throw new ArgumentException("Invalid master password salt."); + throw new BadRequestException("Invalid master password salt."); } } } diff --git a/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs b/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs index d1ab6f645b..cb18ed2a78 100644 --- a/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs +++ b/src/Core/KeyManagement/Models/Data/MasterPasswordUnlockData.cs @@ -1,6 +1,5 @@ -#nullable enable - -using Bit.Core.Entities; +using Bit.Core.Entities; +using Bit.Core.Exceptions; namespace Bit.Core.KeyManagement.Models.Data; @@ -14,7 +13,7 @@ public class MasterPasswordUnlockData { if (user.GetMasterPasswordSalt() != Salt) { - throw new ArgumentException("Invalid master password salt."); + throw new BadRequestException("Invalid master password salt."); } } } diff --git a/src/Core/KeyManagement/Models/Data/PublicKeyEncryptionKeyPairData.cs b/src/Core/KeyManagement/Models/Data/PublicKeyEncryptionKeyPairData.cs new file mode 100644 index 0000000000..fb8b09d390 --- /dev/null +++ b/src/Core/KeyManagement/Models/Data/PublicKeyEncryptionKeyPairData.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Bit.Core.KeyManagement.Models.Data; + + +public class PublicKeyEncryptionKeyPairData +{ + public required string WrappedPrivateKey { get; set; } + public string? SignedPublicKey { get; set; } + public required string PublicKey { get; set; } + + [JsonConstructor] + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public PublicKeyEncryptionKeyPairData(string wrappedPrivateKey, string publicKey, string? signedPublicKey = null) + { + WrappedPrivateKey = wrappedPrivateKey ?? throw new ArgumentNullException(nameof(wrappedPrivateKey)); + PublicKey = publicKey ?? throw new ArgumentNullException(nameof(publicKey)); + SignedPublicKey = signedPublicKey; + } +} diff --git a/src/Core/KeyManagement/Models/Data/RotateUserAccountKeysData.cs b/src/Core/KeyManagement/Models/Data/RotateUserAccountKeysData.cs index 557fb56ff3..19d14b273f 100644 --- a/src/Core/KeyManagement/Models/Data/RotateUserAccountKeysData.cs +++ b/src/Core/KeyManagement/Models/Data/RotateUserAccountKeysData.cs @@ -1,6 +1,4 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - + using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Data; using Bit.Core.Entities; @@ -12,21 +10,19 @@ namespace Bit.Core.KeyManagement.Models.Data; public class RotateUserAccountKeysData { // Authentication for this requests - public string OldMasterKeyAuthenticationHash { get; set; } + public required string OldMasterKeyAuthenticationHash { get; set; } - // Other keys encrypted by the userkey - public string UserKeyEncryptedAccountPrivateKey { get; set; } - public string AccountPublicKey { get; set; } + public required UserAccountKeysData AccountKeys { get; set; } // All methods to get to the userkey - public MasterPasswordUnlockAndAuthenticationData MasterPasswordUnlockData { get; set; } - public IEnumerable EmergencyAccesses { get; set; } - public IReadOnlyList OrganizationUsers { get; set; } - public IEnumerable WebAuthnKeys { get; set; } - public IEnumerable DeviceKeys { get; set; } + public required MasterPasswordUnlockAndAuthenticationData MasterPasswordUnlockData { get; set; } + public required IEnumerable EmergencyAccesses { get; set; } + public required IReadOnlyList OrganizationUsers { get; set; } + public required IEnumerable WebAuthnKeys { get; set; } + public required IEnumerable DeviceKeys { get; set; } // User vault data encrypted by the userkey - public IEnumerable Ciphers { get; set; } - public IEnumerable Folders { get; set; } - public IReadOnlyList Sends { get; set; } + public required IEnumerable Ciphers { get; set; } + public required IEnumerable Folders { get; set; } + public required IReadOnlyList Sends { get; set; } } diff --git a/src/Core/KeyManagement/Models/Data/SecurityStateData.cs b/src/Core/KeyManagement/Models/Data/SecurityStateData.cs new file mode 100644 index 0000000000..c9a4610387 --- /dev/null +++ b/src/Core/KeyManagement/Models/Data/SecurityStateData.cs @@ -0,0 +1,10 @@ + +namespace Bit.Core.KeyManagement.Models.Data; + +public class SecurityStateData +{ + public required string SecurityState { get; set; } + // The security version is included in the security state, but needs COSE parsing, + // so this is a separate copy that can be used directly. + public required int SecurityVersion { get; set; } +} diff --git a/src/Core/KeyManagement/Models/Data/SignatureKeyPairData.cs b/src/Core/KeyManagement/Models/Data/SignatureKeyPairData.cs new file mode 100644 index 0000000000..32ae3eef8f --- /dev/null +++ b/src/Core/KeyManagement/Models/Data/SignatureKeyPairData.cs @@ -0,0 +1,21 @@ + +using System.Text.Json.Serialization; +using Bit.Core.KeyManagement.Enums; + +namespace Bit.Core.KeyManagement.Models.Data; + +public class SignatureKeyPairData +{ + public required SignatureAlgorithm SignatureAlgorithm { get; set; } + public required string WrappedSigningKey { get; set; } + public required string VerifyingKey { get; set; } + + [JsonConstructor] + [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute] + public SignatureKeyPairData(SignatureAlgorithm signatureAlgorithm, string wrappedSigningKey, string verifyingKey) + { + SignatureAlgorithm = signatureAlgorithm; + WrappedSigningKey = wrappedSigningKey ?? throw new ArgumentNullException(nameof(wrappedSigningKey)); + VerifyingKey = verifyingKey ?? throw new ArgumentNullException(nameof(verifyingKey)); + } +} diff --git a/src/Core/KeyManagement/Models/Data/UserAccountKeysData.cs b/src/Core/KeyManagement/Models/Data/UserAccountKeysData.cs new file mode 100644 index 0000000000..cabdca59ea --- /dev/null +++ b/src/Core/KeyManagement/Models/Data/UserAccountKeysData.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.KeyManagement.Models.Data; + + +public class UserAccountKeysData +{ + public required PublicKeyEncryptionKeyPairData PublicKeyEncryptionKeyPairData { get; set; } + public SignatureKeyPairData? SignatureKeyPairData { get; set; } + public SecurityStateData? SecurityStateData { get; set; } +} diff --git a/src/Core/KeyManagement/Queries/Interfaces/IUserAcountKeysQuery.cs b/src/Core/KeyManagement/Queries/Interfaces/IUserAcountKeysQuery.cs new file mode 100644 index 0000000000..4ea9b7582b --- /dev/null +++ b/src/Core/KeyManagement/Queries/Interfaces/IUserAcountKeysQuery.cs @@ -0,0 +1,10 @@ + +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Data; + +namespace Bit.Core.KeyManagement.Queries.Interfaces; + +public interface IUserAccountKeysQuery +{ + Task Run(User user); +} diff --git a/src/Core/KeyManagement/Queries/UserAccountKeysQuery.cs b/src/Core/KeyManagement/Queries/UserAccountKeysQuery.cs new file mode 100644 index 0000000000..7aafd2cf1e --- /dev/null +++ b/src/Core/KeyManagement/Queries/UserAccountKeysQuery.cs @@ -0,0 +1,35 @@ + +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries.Interfaces; +using Bit.Core.KeyManagement.Repositories; + +namespace Bit.Core.KeyManagement.Queries; + + +public class UserAccountKeysQuery(IUserSignatureKeyPairRepository signatureKeyPairRepository) : IUserAccountKeysQuery +{ + public async Task Run(User user) + { + if (user.GetSecurityVersion() < 2) + { + return new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + }; + } + else + { + return new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = await signatureKeyPairRepository.GetByUserIdAsync(user.Id), + SecurityStateData = new SecurityStateData + { + SecurityState = user.SecurityState!, + SecurityVersion = user.GetSecurityVersion(), + } + }; + } + } +} diff --git a/src/Core/KeyManagement/Repositories/IUserSignatureKeyPairRepository.cs b/src/Core/KeyManagement/Repositories/IUserSignatureKeyPairRepository.cs new file mode 100644 index 0000000000..ce8979620f --- /dev/null +++ b/src/Core/KeyManagement/Repositories/IUserSignatureKeyPairRepository.cs @@ -0,0 +1,14 @@ + +using Bit.Core.KeyManagement.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Repositories; + +namespace Bit.Core.KeyManagement.Repositories; + +public interface IUserSignatureKeyPairRepository : IRepository +{ + public Task GetByUserIdAsync(Guid userId); + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SignatureKeyPairData signatureKeyPair); + public UpdateEncryptedDataForKeyRotation SetUserSignatureKeyPair(Guid userId, SignatureKeyPairData signatureKeyPair); +} diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs index 91363abee8..c1e7905d78 100644 --- a/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs +++ b/src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs @@ -1,6 +1,11 @@ -using Bit.Core.Auth.Repositories; +// FIXME: Update this file to be null safe and then delete the line below +#nullable disable + +using Bit.Core.Auth.Repositories; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; @@ -25,6 +30,8 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand private readonly IdentityErrorDescriber _identityErrorDescriber; private readonly IWebAuthnCredentialRepository _credentialRepository; private readonly IPasswordHasher _passwordHasher; + private readonly IUserSignatureKeyPairRepository _userSignatureKeyPairRepository; + private readonly IFeatureService _featureService; /// /// Instantiates a new @@ -36,16 +43,19 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand /// Provides a method to update re-encrypted send data /// Provides a method to update re-encrypted emergency access data /// Provides a method to update re-encrypted organization user data + /// Provides a method to update re-encrypted device keys /// Hashes the new master password /// Logs out user from other devices after successful rotation /// Provides a password mismatch error if master password hash validation fails /// Provides a method to update re-encrypted WebAuthn keys + /// Provides a method to update re-encrypted signature keys public RotateUserAccountKeysCommand(IUserService userService, IUserRepository userRepository, ICipherRepository cipherRepository, IFolderRepository folderRepository, ISendRepository sendRepository, IEmergencyAccessRepository emergencyAccessRepository, IOrganizationUserRepository organizationUserRepository, IDeviceRepository deviceRepository, IPasswordHasher passwordHasher, IPushNotificationService pushService, IdentityErrorDescriber errors, IWebAuthnCredentialRepository credentialRepository, + IUserSignatureKeyPairRepository userSignatureKeyPairRepository, IFeatureService featureService) { _userService = userService; @@ -60,6 +70,8 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand _identityErrorDescriber = errors; _credentialRepository = credentialRepository; _passwordHasher = passwordHasher; + _userSignatureKeyPairRepository = userSignatureKeyPairRepository; + _featureService = featureService; } /// @@ -80,50 +92,106 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand user.LastKeyRotationDate = now; user.SecurityStamp = Guid.NewGuid().ToString(); - if ( - !model.MasterPasswordUnlockData.ValidateForUser(user) - ) + List saveEncryptedDataActions = []; + + await UpdateAccountKeysAsync(model, user, saveEncryptedDataActions); + UpdateUnlockMethods(model, user, saveEncryptedDataActions); + UpdateUserData(model, user, saveEncryptedDataActions); + + await _userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, saveEncryptedDataActions); + await _pushService.PushLogOutAsync(user.Id); + return IdentityResult.Success; + } + + public async Task RotateV2AccountKeysAsync(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + ValidateV2Encryption(model); + await ValidateVerifyingKeyUnchangedAsync(model, user); + + saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.UpdateForKeyRotation(user.Id, model.AccountKeys.SignatureKeyPairData)); + user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; + user.SecurityState = model.AccountKeys.SecurityStateData!.SecurityState; + user.SecurityVersion = model.AccountKeys.SecurityStateData.SecurityVersion; + } + + public void UpgradeV1ToV2Keys(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + ValidateV2Encryption(model); + saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.SetUserSignatureKeyPair(user.Id, model.AccountKeys.SignatureKeyPairData)); + user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey; + user.SecurityState = model.AccountKeys.SecurityStateData!.SecurityState; + user.SecurityVersion = model.AccountKeys.SecurityStateData.SecurityVersion; + } + + public async Task UpdateAccountKeysAsync(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + ValidatePublicKeyEncryptionKeyPairUnchanged(model, user); + + if (IsV2EncryptionUserAsync(user)) { - throw new InvalidOperationException("The provided master password unlock data is not valid for this user."); + await RotateV2AccountKeysAsync(model, user, saveEncryptedDataActions); } - if ( - model.AccountPublicKey != user.PublicKey - ) + else if (model.AccountKeys.SignatureKeyPairData != null) { - throw new InvalidOperationException("The provided account public key does not match the user's current public key, and changing the account asymmetric keypair is currently not supported during key rotation."); + UpgradeV1ToV2Keys(model, user, saveEncryptedDataActions); + } + else + { + if (GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.AesCbc256_HmacSha256_B64) + { + throw new InvalidOperationException("The provided account private key was not wrapped with AES-256-CBC-HMAC"); + } + // V1 user to V1 user rotation needs to further changes, the private key was re-encrypted. } - user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey; - user.PrivateKey = model.UserKeyEncryptedAccountPrivateKey; - user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash); - user.MasterPasswordHint = model.MasterPasswordUnlockData.MasterPasswordHint; + // Private key is re-wrapped with new user key by client + user.PrivateKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey; + } + + public void UpdateUserData(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + // The revision date has to be updated so that de-synced clients don't accidentally post over the re-encrypted data + // with an old-user key-encrypted copy + var now = DateTime.UtcNow; - List saveEncryptedDataActions = new(); if (model.Ciphers.Any()) { - saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers)); + var ciphersWithUpdatedDate = model.Ciphers.ToList().Select(c => { c.RevisionDate = now; return c; }); + saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, ciphersWithUpdatedDate)); } if (model.Folders.Any()) { - saveEncryptedDataActions.Add(_folderRepository.UpdateForKeyRotation(user.Id, model.Folders)); + var foldersWithUpdatedDate = model.Folders.ToList().Select(f => { f.RevisionDate = now; return f; }); + saveEncryptedDataActions.Add(_folderRepository.UpdateForKeyRotation(user.Id, foldersWithUpdatedDate)); } if (model.Sends.Any()) { - saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, model.Sends)); + var sendsWithUpdatedDate = model.Sends.ToList().Select(s => { s.RevisionDate = now; return s; }); + saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, sendsWithUpdatedDate)); } + } + + void UpdateUnlockMethods(RotateUserAccountKeysData model, User user, List saveEncryptedDataActions) + { + if (!model.MasterPasswordUnlockData.ValidateForUser(user)) + { + throw new InvalidOperationException("The provided master password unlock data is not valid for this user."); + } + // Update master password authentication & unlock + user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey; + user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash); + user.MasterPasswordHint = model.MasterPasswordUnlockData.MasterPasswordHint; if (model.EmergencyAccesses.Any()) { - saveEncryptedDataActions.Add( - _emergencyAccessRepository.UpdateForKeyRotation(user.Id, model.EmergencyAccesses)); + saveEncryptedDataActions.Add(_emergencyAccessRepository.UpdateForKeyRotation(user.Id, model.EmergencyAccesses)); } if (model.OrganizationUsers.Any()) { - saveEncryptedDataActions.Add( - _organizationUserRepository.UpdateForKeyRotation(user.Id, model.OrganizationUsers)); + saveEncryptedDataActions.Add(_organizationUserRepository.UpdateForKeyRotation(user.Id, model.OrganizationUsers)); } if (model.WebAuthnKeys.Any()) @@ -135,9 +203,80 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand { saveEncryptedDataActions.Add(_deviceRepository.UpdateKeysForRotationAsync(user.Id, model.DeviceKeys)); } + } - await _userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, saveEncryptedDataActions); - await _pushService.PushLogOutAsync(user.Id); - return IdentityResult.Success; + private bool IsV2EncryptionUserAsync(User user) + { + // Returns whether the user is a V2 user based on the private key's encryption type. + ArgumentNullException.ThrowIfNull(user); + var isPrivateKeyEncryptionV2 = GetEncryptionType(user.PrivateKey) == EncryptionType.XChaCha20Poly1305_B64; + return isPrivateKeyEncryptionV2; + } + + private async Task ValidateVerifyingKeyUnchangedAsync(RotateUserAccountKeysData model, User user) + { + var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id) ?? throw new InvalidOperationException("User does not have a signature key pair."); + if (model.AccountKeys.SignatureKeyPairData.VerifyingKey != currentSignatureKeyPair!.VerifyingKey) + { + throw new InvalidOperationException("The provided verifying key does not match the user's current verifying key."); + } + } + + private static void ValidatePublicKeyEncryptionKeyPairUnchanged(RotateUserAccountKeysData model, User user) + { + var publicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey; + if (publicKey != user.PublicKey) + { + throw new InvalidOperationException("The provided account public key does not match the user's current public key, and changing the account asymmetric key pair is currently not supported during key rotation."); + } + } + + private static void ValidateV2Encryption(RotateUserAccountKeysData model) + { + if (model.AccountKeys.SignatureKeyPairData == null) + { + throw new InvalidOperationException("Signature key pair data is required for V2 encryption."); + } + if (GetEncryptionType(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey) != EncryptionType.XChaCha20Poly1305_B64) + { + throw new InvalidOperationException("The provided signing key data is not wrapped with XChaCha20-Poly1305."); + } + if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.VerifyingKey)) + { + throw new InvalidOperationException("The provided signature key pair data does not contain a valid verifying key."); + } + + if (GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.XChaCha20Poly1305_B64) + { + throw new InvalidOperationException("The provided private key encryption key is not wrapped with XChaCha20-Poly1305."); + } + if (string.IsNullOrEmpty(model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey)) + { + throw new InvalidOperationException("No signed public key provided, but the user already has a signature key pair."); + } + if (model.AccountKeys.SecurityStateData == null || string.IsNullOrEmpty(model.AccountKeys.SecurityStateData.SecurityState)) + { + throw new InvalidOperationException("No signed security state provider for V2 user"); + } + } + + /// + /// Helper method to convert an encryption type string to an enum value. + /// + private static EncryptionType GetEncryptionType(string encString) + { + var parts = encString.Split('.'); + if (parts.Length == 1) + { + throw new ArgumentException("Invalid encryption type string."); + } + if (byte.TryParse(parts[0], out var encryptionTypeNumber)) + { + if (Enum.IsDefined(typeof(EncryptionType), encryptionTypeNumber)) + { + return (EncryptionType)encryptionTypeNumber; + } + } + throw new ArgumentException("Invalid encryption type string."); } } diff --git a/src/Core/Models/Business/SubscriptionInfo.cs b/src/Core/Models/Business/SubscriptionInfo.cs index a016ac54f3..f8a96a189f 100644 --- a/src/Core/Models/Business/SubscriptionInfo.cs +++ b/src/Core/Models/Business/SubscriptionInfo.cs @@ -1,6 +1,7 @@ // FIXME: Update this file to be null safe and then delete the line below #nullable disable +using Bit.Core.Billing.Extensions; using Stripe; namespace Bit.Core.Models.Business; @@ -36,8 +37,13 @@ public class SubscriptionInfo Status = sub.Status; TrialStartDate = sub.TrialStart; TrialEndDate = sub.TrialEnd; - PeriodStartDate = sub.CurrentPeriodStart; - PeriodEndDate = sub.CurrentPeriodEnd; + var currentPeriod = sub.GetCurrentPeriod(); + if (currentPeriod != null) + { + var (start, end) = currentPeriod.Value; + PeriodStartDate = start; + PeriodEndDate = end; + } CancelledDate = sub.CanceledAt; CancelAtEndDate = sub.CancelAtPeriodEnd; Cancelled = sub.Status == "canceled" || sub.Status == "unpaid" || sub.Status == "incomplete_expired"; diff --git a/src/Core/Models/PushNotification.cs b/src/Core/Models/PushNotification.cs index c4ae1e2858..a622b98e05 100644 --- a/src/Core/Models/PushNotification.cs +++ b/src/Core/Models/PushNotification.cs @@ -97,3 +97,9 @@ public class ProviderBankAccountVerifiedPushNotification public Guid ProviderId { get; set; } public Guid AdminId { get; set; } } + +public class LogOutPushNotification +{ + public Guid UserId { get; set; } + public PushNotificationLogOutReason? Reason { get; set; } +} diff --git a/src/Core/Models/Stripe/StripeSubscriptionListOptions.cs b/src/Core/Models/Stripe/StripeSubscriptionListOptions.cs deleted file mode 100644 index 34662ecdbb..0000000000 --- a/src/Core/Models/Stripe/StripeSubscriptionListOptions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// FIXME: Update this file to be null safe and then delete the line below -#nullable disable - -namespace Bit.Core.Models.BitStripe; - -// Stripe's SubscriptionListOptions model has a complex input for date filters. -// It expects a dictionary, and has lots of validation rules around what can have a value and what can't. -// To simplify this a bit we are extending Stripe's model and using our own date inputs, and building the dictionary they expect JiT. -// ___ -// Our model also facilitates selecting all elements in a list, which is unsupported by Stripe's model. -public class StripeSubscriptionListOptions : Stripe.SubscriptionListOptions -{ - public DateTime? CurrentPeriodEndDate { get; set; } - public string CurrentPeriodEndRange { get; set; } = "lt"; - public bool SelectAll { get; set; } - public new Stripe.DateRangeOptions CurrentPeriodEnd - { - get - { - return CurrentPeriodEndDate.HasValue ? - new Stripe.DateRangeOptions() - { - LessThan = CurrentPeriodEndRange == "lt" ? CurrentPeriodEndDate : null, - GreaterThan = CurrentPeriodEndRange == "gt" ? CurrentPeriodEndDate : null - } : - null; - } - } - - public Stripe.SubscriptionListOptions ToStripeApiOptions() - { - var stripeApiOptions = (Stripe.SubscriptionListOptions)this; - - if (SelectAll) - { - stripeApiOptions.EndingBefore = null; - stripeApiOptions.StartingAfter = null; - } - - if (CurrentPeriodEndDate.HasValue) - { - stripeApiOptions.CurrentPeriodEnd = new Stripe.DateRangeOptions() - { - LessThan = CurrentPeriodEndRange == "lt" ? CurrentPeriodEndDate : null, - GreaterThan = CurrentPeriodEndRange == "gt" ? CurrentPeriodEndDate : null - }; - } - - return stripeApiOptions; - } -} diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs index 9a995a9cf0..965e0cf2a9 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs @@ -62,7 +62,7 @@ public class SelfHostedSyncSponsorshipsCommand : BaseIdentityClientService, ISel .ToDictionary(i => i.SponsoringOrganizationUserId); if (!organizationSponsorshipsDict.Any()) { - _logger.LogInformation($"No existing sponsorships to sync for organization {organizationId}"); + _logger.LogInformation("No existing sponsorships to sync for organization {organizationId}", organizationId); return; } var syncedSponsorships = new List(); diff --git a/src/Core/Platform/Push/IPushNotificationService.cs b/src/Core/Platform/Push/IPushNotificationService.cs index 32a488b827..b6d7d4d416 100644 --- a/src/Core/Platform/Push/IPushNotificationService.cs +++ b/src/Core/Platform/Push/IPushNotificationService.cs @@ -167,18 +167,17 @@ public interface IPushNotificationService ExcludeCurrentContext = false, }); - Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false) - => PushAsync(new PushNotification + Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false, + PushNotificationLogOutReason? reason = null) + => PushAsync(new PushNotification { Type = PushType.LogOut, Target = NotificationTarget.User, TargetId = userId, - Payload = new UserPushNotification + Payload = new LogOutPushNotification { UserId = userId, -#pragma warning disable BWP0001 // Type or member is obsolete - Date = TimeProvider.GetUtcNow().UtcDateTime, -#pragma warning restore BWP0001 // Type or member is obsolete + Reason = reason }, ExcludeCurrentContext = excludeCurrentContextFromPush, }); diff --git a/src/Core/Platform/Push/PushType.cs b/src/Core/Platform/Push/PushType.cs index 7765c1aa66..93eca86243 100644 --- a/src/Core/Platform/Push/PushType.cs +++ b/src/Core/Platform/Push/PushType.cs @@ -55,7 +55,7 @@ public enum PushType : byte [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] SyncSettings = 10, - [NotificationInfo("not-specified", typeof(Models.UserPushNotification))] + [NotificationInfo("not-specified", typeof(Models.LogOutPushNotification))] LogOut = 11, [NotificationInfo("@bitwarden/team-tools-dev", typeof(Models.SyncSendPushNotification))] diff --git a/src/Core/SecretsManager/Entities/SecretVersion.cs b/src/Core/SecretsManager/Entities/SecretVersion.cs new file mode 100644 index 0000000000..cee447bd2a --- /dev/null +++ b/src/Core/SecretsManager/Entities/SecretVersion.cs @@ -0,0 +1,28 @@ +#nullable enable +using Bit.Core.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.SecretsManager.Entities; + +public class SecretVersion : ITableObject +{ + public Guid Id { get; set; } + + public Guid SecretId { get; set; } + + public string Value { get; set; } = string.Empty; + + public DateTime VersionDate { get; set; } + + public Guid? EditorServiceAccountId { get; set; } + + public Guid? EditorOrganizationUserId { get; set; } + + public void SetNewId() + { + if (Id == default(Guid)) + { + Id = CoreHelpers.GenerateComb(); + } + } +} diff --git a/src/Core/Services/IStripeAdapter.cs b/src/Core/Services/IStripeAdapter.cs index 8a41263956..6b2c3c299e 100644 --- a/src/Core/Services/IStripeAdapter.cs +++ b/src/Core/Services/IStripeAdapter.cs @@ -3,58 +3,47 @@ using Bit.Core.Models.BitStripe; using Stripe; +using Stripe.Tax; namespace Bit.Core.Services; public interface IStripeAdapter { - Task CustomerCreateAsync(Stripe.CustomerCreateOptions customerCreateOptions); - Task CustomerGetAsync(string id, Stripe.CustomerGetOptions options = null); - Task CustomerUpdateAsync(string id, Stripe.CustomerUpdateOptions options = null); - Task CustomerDeleteAsync(string id); - Task> CustomerListPaymentMethods(string id, CustomerListPaymentMethodsOptions options = null); + Task CustomerCreateAsync(CustomerCreateOptions customerCreateOptions); + Task CustomerDeleteDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null); + Task CustomerGetAsync(string id, CustomerGetOptions options = null); + Task CustomerUpdateAsync(string id, CustomerUpdateOptions options = null); + Task CustomerDeleteAsync(string id); + Task> CustomerListPaymentMethods(string id, CustomerPaymentMethodListOptions options = null); Task CustomerBalanceTransactionCreate(string customerId, CustomerBalanceTransactionCreateOptions options); - Task SubscriptionCreateAsync(Stripe.SubscriptionCreateOptions subscriptionCreateOptions); - Task SubscriptionGetAsync(string id, Stripe.SubscriptionGetOptions options = null); - - /// - /// Retrieves a subscription object for a provider. - /// - /// The subscription ID. - /// The provider ID. - /// Additional options. - /// The subscription object. - /// Thrown when the subscription doesn't belong to the provider. - Task ProviderSubscriptionGetAsync(string id, Guid providerId, Stripe.SubscriptionGetOptions options = null); - - Task> SubscriptionListAsync(StripeSubscriptionListOptions subscriptionSearchOptions); - Task SubscriptionUpdateAsync(string id, Stripe.SubscriptionUpdateOptions options = null); - Task SubscriptionCancelAsync(string Id, Stripe.SubscriptionCancelOptions options = null); - Task InvoiceUpcomingAsync(Stripe.UpcomingInvoiceOptions options); - Task InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options); - Task> InvoiceListAsync(StripeInvoiceListOptions options); - Task InvoiceCreatePreviewAsync(InvoiceCreatePreviewOptions options); - Task> InvoiceSearchAsync(InvoiceSearchOptions options); - Task InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options); - Task InvoiceFinalizeInvoiceAsync(string id, Stripe.InvoiceFinalizeOptions options); - Task InvoiceSendInvoiceAsync(string id, Stripe.InvoiceSendOptions options); - Task InvoicePayAsync(string id, Stripe.InvoicePayOptions options = null); - Task InvoiceDeleteAsync(string id, Stripe.InvoiceDeleteOptions options = null); - Task InvoiceVoidInvoiceAsync(string id, Stripe.InvoiceVoidOptions options = null); - IEnumerable PaymentMethodListAutoPaging(Stripe.PaymentMethodListOptions options); - IAsyncEnumerable PaymentMethodListAutoPagingAsync(Stripe.PaymentMethodListOptions options); - Task PaymentMethodAttachAsync(string id, Stripe.PaymentMethodAttachOptions options = null); - Task PaymentMethodDetachAsync(string id, Stripe.PaymentMethodDetachOptions options = null); - Task TaxIdCreateAsync(string id, Stripe.TaxIdCreateOptions options); - Task TaxIdDeleteAsync(string customerId, string taxIdId, Stripe.TaxIdDeleteOptions options = null); - Task> TaxRegistrationsListAsync(Stripe.Tax.RegistrationListOptions options = null); - Task> ChargeListAsync(Stripe.ChargeListOptions options); - Task RefundCreateAsync(Stripe.RefundCreateOptions options); - Task CardDeleteAsync(string customerId, string cardId, Stripe.CardDeleteOptions options = null); - Task BankAccountCreateAsync(string customerId, Stripe.BankAccountCreateOptions options = null); - Task BankAccountDeleteAsync(string customerId, string bankAccount, Stripe.BankAccountDeleteOptions options = null); - Task> PriceListAsync(Stripe.PriceListOptions options = null); + Task SubscriptionCreateAsync(SubscriptionCreateOptions subscriptionCreateOptions); + Task SubscriptionGetAsync(string id, SubscriptionGetOptions options = null); + Task SubscriptionUpdateAsync(string id, SubscriptionUpdateOptions options = null); + Task SubscriptionCancelAsync(string Id, SubscriptionCancelOptions options = null); + Task InvoiceGetAsync(string id, InvoiceGetOptions options); + Task> InvoiceListAsync(StripeInvoiceListOptions options); + Task InvoiceCreatePreviewAsync(InvoiceCreatePreviewOptions options); + Task> InvoiceSearchAsync(InvoiceSearchOptions options); + Task InvoiceUpdateAsync(string id, InvoiceUpdateOptions options); + Task InvoiceFinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options); + Task InvoiceSendInvoiceAsync(string id, InvoiceSendOptions options); + Task InvoicePayAsync(string id, InvoicePayOptions options = null); + Task InvoiceDeleteAsync(string id, InvoiceDeleteOptions options = null); + Task InvoiceVoidInvoiceAsync(string id, InvoiceVoidOptions options = null); + IEnumerable PaymentMethodListAutoPaging(PaymentMethodListOptions options); + IAsyncEnumerable PaymentMethodListAutoPagingAsync(PaymentMethodListOptions options); + Task PaymentMethodAttachAsync(string id, PaymentMethodAttachOptions options = null); + Task PaymentMethodDetachAsync(string id, PaymentMethodDetachOptions options = null); + Task TaxIdCreateAsync(string id, TaxIdCreateOptions options); + Task TaxIdDeleteAsync(string customerId, string taxIdId, TaxIdDeleteOptions options = null); + Task> TaxRegistrationsListAsync(RegistrationListOptions options = null); + Task> ChargeListAsync(ChargeListOptions options); + Task RefundCreateAsync(RefundCreateOptions options); + Task CardDeleteAsync(string customerId, string cardId, CardDeleteOptions options = null); + Task BankAccountCreateAsync(string customerId, BankAccountCreateOptions options = null); + Task BankAccountDeleteAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null); + Task> PriceListAsync(PriceListOptions options = null); Task SetupIntentCreate(SetupIntentCreateOptions options); Task> SetupIntentList(SetupIntentListOptions options); Task SetupIntentCancel(string id, SetupIntentCancelOptions options = null); diff --git a/src/Core/Services/Implementations/StripeAdapter.cs b/src/Core/Services/Implementations/StripeAdapter.cs index 4863baf73e..3d1663f021 100644 --- a/src/Core/Services/Implementations/StripeAdapter.cs +++ b/src/Core/Services/Implementations/StripeAdapter.cs @@ -9,18 +9,18 @@ namespace Bit.Core.Services; public class StripeAdapter : IStripeAdapter { - private readonly Stripe.CustomerService _customerService; - private readonly Stripe.SubscriptionService _subscriptionService; - private readonly Stripe.InvoiceService _invoiceService; - private readonly Stripe.PaymentMethodService _paymentMethodService; - private readonly Stripe.TaxIdService _taxIdService; - private readonly Stripe.ChargeService _chargeService; - private readonly Stripe.RefundService _refundService; - private readonly Stripe.CardService _cardService; - private readonly Stripe.BankAccountService _bankAccountService; - private readonly Stripe.PlanService _planService; - private readonly Stripe.PriceService _priceService; - private readonly Stripe.SetupIntentService _setupIntentService; + private readonly CustomerService _customerService; + private readonly SubscriptionService _subscriptionService; + private readonly InvoiceService _invoiceService; + private readonly PaymentMethodService _paymentMethodService; + private readonly TaxIdService _taxIdService; + private readonly ChargeService _chargeService; + private readonly RefundService _refundService; + private readonly CardService _cardService; + private readonly BankAccountService _bankAccountService; + private readonly PlanService _planService; + private readonly PriceService _priceService; + private readonly SetupIntentService _setupIntentService; private readonly Stripe.TestHelpers.TestClockService _testClockService; private readonly CustomerBalanceTransactionService _customerBalanceTransactionService; private readonly Stripe.Tax.RegistrationService _taxRegistrationService; @@ -28,17 +28,17 @@ public class StripeAdapter : IStripeAdapter public StripeAdapter() { - _customerService = new Stripe.CustomerService(); - _subscriptionService = new Stripe.SubscriptionService(); - _invoiceService = new Stripe.InvoiceService(); - _paymentMethodService = new Stripe.PaymentMethodService(); - _taxIdService = new Stripe.TaxIdService(); - _chargeService = new Stripe.ChargeService(); - _refundService = new Stripe.RefundService(); - _cardService = new Stripe.CardService(); - _bankAccountService = new Stripe.BankAccountService(); - _priceService = new Stripe.PriceService(); - _planService = new Stripe.PlanService(); + _customerService = new CustomerService(); + _subscriptionService = new SubscriptionService(); + _invoiceService = new InvoiceService(); + _paymentMethodService = new PaymentMethodService(); + _taxIdService = new TaxIdService(); + _chargeService = new ChargeService(); + _refundService = new RefundService(); + _cardService = new CardService(); + _bankAccountService = new BankAccountService(); + _priceService = new PriceService(); + _planService = new PlanService(); _setupIntentService = new SetupIntentService(); _testClockService = new Stripe.TestHelpers.TestClockService(); _customerBalanceTransactionService = new CustomerBalanceTransactionService(); @@ -46,28 +46,31 @@ public class StripeAdapter : IStripeAdapter _calculationService = new CalculationService(); } - public Task CustomerCreateAsync(Stripe.CustomerCreateOptions options) + public Task CustomerCreateAsync(CustomerCreateOptions options) { return _customerService.CreateAsync(options); } - public Task CustomerGetAsync(string id, Stripe.CustomerGetOptions options = null) + public Task CustomerDeleteDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null) => + _customerService.DeleteDiscountAsync(customerId, options); + + public Task CustomerGetAsync(string id, CustomerGetOptions options = null) { return _customerService.GetAsync(id, options); } - public Task CustomerUpdateAsync(string id, Stripe.CustomerUpdateOptions options = null) + public Task CustomerUpdateAsync(string id, CustomerUpdateOptions options = null) { return _customerService.UpdateAsync(id, options); } - public Task CustomerDeleteAsync(string id) + public Task CustomerDeleteAsync(string id) { return _customerService.DeleteAsync(id); } public async Task> CustomerListPaymentMethods(string id, - CustomerListPaymentMethodsOptions options = null) + CustomerPaymentMethodListOptions options = null) { var paymentMethods = await _customerService.ListPaymentMethodsAsync(id, options); return paymentMethods.Data; @@ -77,12 +80,12 @@ public class StripeAdapter : IStripeAdapter CustomerBalanceTransactionCreateOptions options) => await _customerBalanceTransactionService.CreateAsync(customerId, options); - public Task SubscriptionCreateAsync(Stripe.SubscriptionCreateOptions options) + public Task SubscriptionCreateAsync(SubscriptionCreateOptions options) { return _subscriptionService.CreateAsync(options); } - public Task SubscriptionGetAsync(string id, Stripe.SubscriptionGetOptions options = null) + public Task SubscriptionGetAsync(string id, SubscriptionGetOptions options = null) { return _subscriptionService.GetAsync(id, options); } @@ -101,28 +104,23 @@ public class StripeAdapter : IStripeAdapter throw new InvalidOperationException("Subscription does not belong to the provider."); } - public Task SubscriptionUpdateAsync(string id, - Stripe.SubscriptionUpdateOptions options = null) + public Task SubscriptionUpdateAsync(string id, + SubscriptionUpdateOptions options = null) { return _subscriptionService.UpdateAsync(id, options); } - public Task SubscriptionCancelAsync(string Id, Stripe.SubscriptionCancelOptions options = null) + public Task SubscriptionCancelAsync(string Id, SubscriptionCancelOptions options = null) { return _subscriptionService.CancelAsync(Id, options); } - public Task InvoiceUpcomingAsync(Stripe.UpcomingInvoiceOptions options) - { - return _invoiceService.UpcomingAsync(options); - } - - public Task InvoiceGetAsync(string id, Stripe.InvoiceGetOptions options) + public Task InvoiceGetAsync(string id, InvoiceGetOptions options) { return _invoiceService.GetAsync(id, options); } - public async Task> InvoiceListAsync(StripeInvoiceListOptions options) + public async Task> InvoiceListAsync(StripeInvoiceListOptions options) { if (!options.SelectAll) { @@ -131,7 +129,7 @@ public class StripeAdapter : IStripeAdapter options.Limit = 100; - var invoices = new List(); + var invoices = new List(); await foreach (var invoice in _invoiceService.ListAutoPagingAsync(options.ToInvoiceListOptions())) { @@ -146,120 +144,104 @@ public class StripeAdapter : IStripeAdapter return _invoiceService.CreatePreviewAsync(options); } - public async Task> InvoiceSearchAsync(InvoiceSearchOptions options) + public async Task> InvoiceSearchAsync(InvoiceSearchOptions options) => (await _invoiceService.SearchAsync(options)).Data; - public Task InvoiceUpdateAsync(string id, Stripe.InvoiceUpdateOptions options) + public Task InvoiceUpdateAsync(string id, InvoiceUpdateOptions options) { return _invoiceService.UpdateAsync(id, options); } - public Task InvoiceFinalizeInvoiceAsync(string id, Stripe.InvoiceFinalizeOptions options) + public Task InvoiceFinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options) { return _invoiceService.FinalizeInvoiceAsync(id, options); } - public Task InvoiceSendInvoiceAsync(string id, Stripe.InvoiceSendOptions options) + public Task InvoiceSendInvoiceAsync(string id, InvoiceSendOptions options) { return _invoiceService.SendInvoiceAsync(id, options); } - public Task InvoicePayAsync(string id, Stripe.InvoicePayOptions options = null) + public Task InvoicePayAsync(string id, InvoicePayOptions options = null) { return _invoiceService.PayAsync(id, options); } - public Task InvoiceDeleteAsync(string id, Stripe.InvoiceDeleteOptions options = null) + public Task InvoiceDeleteAsync(string id, InvoiceDeleteOptions options = null) { return _invoiceService.DeleteAsync(id, options); } - public Task InvoiceVoidInvoiceAsync(string id, Stripe.InvoiceVoidOptions options = null) + public Task InvoiceVoidInvoiceAsync(string id, InvoiceVoidOptions options = null) { return _invoiceService.VoidInvoiceAsync(id, options); } - public IEnumerable PaymentMethodListAutoPaging(Stripe.PaymentMethodListOptions options) + public IEnumerable PaymentMethodListAutoPaging(PaymentMethodListOptions options) { return _paymentMethodService.ListAutoPaging(options); } - public IAsyncEnumerable PaymentMethodListAutoPagingAsync(Stripe.PaymentMethodListOptions options) + public IAsyncEnumerable PaymentMethodListAutoPagingAsync(PaymentMethodListOptions options) => _paymentMethodService.ListAutoPagingAsync(options); - public Task PaymentMethodAttachAsync(string id, Stripe.PaymentMethodAttachOptions options = null) + public Task PaymentMethodAttachAsync(string id, PaymentMethodAttachOptions options = null) { return _paymentMethodService.AttachAsync(id, options); } - public Task PaymentMethodDetachAsync(string id, Stripe.PaymentMethodDetachOptions options = null) + public Task PaymentMethodDetachAsync(string id, PaymentMethodDetachOptions options = null) { return _paymentMethodService.DetachAsync(id, options); } - public Task PlanGetAsync(string id, Stripe.PlanGetOptions options = null) + public Task PlanGetAsync(string id, PlanGetOptions options = null) { return _planService.GetAsync(id, options); } - public Task TaxIdCreateAsync(string id, Stripe.TaxIdCreateOptions options) + public Task TaxIdCreateAsync(string id, TaxIdCreateOptions options) { return _taxIdService.CreateAsync(id, options); } - public Task TaxIdDeleteAsync(string customerId, string taxIdId, - Stripe.TaxIdDeleteOptions options = null) + public Task TaxIdDeleteAsync(string customerId, string taxIdId, + TaxIdDeleteOptions options = null) { return _taxIdService.DeleteAsync(customerId, taxIdId); } - public Task> TaxRegistrationsListAsync(Stripe.Tax.RegistrationListOptions options = null) + public Task> TaxRegistrationsListAsync(RegistrationListOptions options = null) { return _taxRegistrationService.ListAsync(options); } - public Task> ChargeListAsync(Stripe.ChargeListOptions options) + public Task> ChargeListAsync(ChargeListOptions options) { return _chargeService.ListAsync(options); } - public Task RefundCreateAsync(Stripe.RefundCreateOptions options) + public Task RefundCreateAsync(RefundCreateOptions options) { return _refundService.CreateAsync(options); } - public Task CardDeleteAsync(string customerId, string cardId, Stripe.CardDeleteOptions options = null) + public Task CardDeleteAsync(string customerId, string cardId, CardDeleteOptions options = null) { return _cardService.DeleteAsync(customerId, cardId, options); } - public Task BankAccountCreateAsync(string customerId, Stripe.BankAccountCreateOptions options = null) + public Task BankAccountCreateAsync(string customerId, BankAccountCreateOptions options = null) { return _bankAccountService.CreateAsync(customerId, options); } - public Task BankAccountDeleteAsync(string customerId, string bankAccount, Stripe.BankAccountDeleteOptions options = null) + public Task BankAccountDeleteAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null) { return _bankAccountService.DeleteAsync(customerId, bankAccount, options); } - public async Task> SubscriptionListAsync(StripeSubscriptionListOptions options) - { - if (!options.SelectAll) - { - return (await _subscriptionService.ListAsync(options.ToStripeApiOptions())).Data; - } - - options.Limit = 100; - var items = new List(); - await foreach (var i in _subscriptionService.ListAutoPagingAsync(options.ToStripeApiOptions())) - { - items.Add(i); - } - return items; - } - - public async Task> PriceListAsync(Stripe.PriceListOptions options = null) + public async Task> PriceListAsync(PriceListOptions options = null) { return await _priceService.ListAsync(options); } diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 5b68906d8a..bb53933d02 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -65,19 +65,20 @@ public class StripePaymentService : IPaymentService bool applySponsorship) { var existingPlan = await _pricingClient.GetPlanOrThrow(org.PlanType); - var sponsoredPlan = sponsorship?.PlanSponsorshipType != null ? - Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) : - null; - var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship); + var sponsoredPlan = sponsorship?.PlanSponsorshipType != null + ? Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) + : null; + var subscriptionUpdate = + new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship); await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, true); var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId); - org.ExpirationDate = sub.CurrentPeriodEnd; + org.ExpirationDate = sub.GetCurrentPeriodEnd(); if (sponsorship is not null) { - sponsorship.ValidUntil = sub.CurrentPeriodEnd; + sponsorship.ValidUntil = sub.GetCurrentPeriodEnd(); } } @@ -100,7 +101,8 @@ public class StripePaymentService : IPaymentService if (sub.Status == SubscriptionStatuses.Canceled) { - throw new BadRequestException("You do not have an active subscription. Reinstate your subscription to make changes."); + throw new BadRequestException( + "You do not have an active subscription. Reinstate your subscription to make changes."); } var existingCoupon = sub.Customer.Discount?.Coupon?.Id; @@ -191,24 +193,24 @@ public class StripePaymentService : IPaymentService throw; } } - else if (!invoice.Paid) + else if (invoice.Status != StripeConstants.InvoiceStatus.Paid) { // Pay invoice with no charge to the customer this completes the invoice immediately without waiting the scheduled 1h invoice = await _stripeAdapter.InvoicePayAsync(subResponse.LatestInvoiceId); paymentIntentClientSecret = null; } - } finally { // Change back the subscription collection method and/or days until due if (collectionMethod != "send_invoice" || daysUntilDue == null) { - await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions - { - CollectionMethod = collectionMethod, - DaysUntilDue = daysUntilDue, - }); + await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, + new SubscriptionUpdateOptions + { + CollectionMethod = collectionMethod, + DaysUntilDue = daysUntilDue, + }); } var customer = await _stripeAdapter.CustomerGetAsync(sub.CustomerId); @@ -218,9 +220,15 @@ public class StripePaymentService : IPaymentService if (!string.IsNullOrEmpty(existingCoupon) && string.IsNullOrEmpty(newCoupon)) { // Re-add the lost coupon due to the update. - await _stripeAdapter.CustomerUpdateAsync(sub.CustomerId, new CustomerUpdateOptions + await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions { - Coupon = existingCoupon + Discounts = + [ + new SubscriptionDiscountOptions + { + Coupon = existingCoupon + } + ] }); } } @@ -352,7 +360,7 @@ public class StripePaymentService : IPaymentService { var hasDefaultCardPaymentMethod = customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card"; var hasDefaultValidSource = customer.DefaultSource != null && - (customer.DefaultSource is Card || customer.DefaultSource is BankAccount); + (customer.DefaultSource is Card || customer.DefaultSource is BankAccount); if (!hasDefaultCardPaymentMethod && !hasDefaultValidSource) { cardPaymentMethodId = GetLatestCardPaymentMethod(customer.Id)?.Id; @@ -365,12 +373,11 @@ public class StripePaymentService : IPaymentService } catch { - await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new InvoiceFinalizeOptions - { - AutoAdvance = false - }); + await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, + new InvoiceFinalizeOptions { AutoAdvance = false }); await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id); } + throw new BadRequestException("No payment method is available."); } } @@ -381,14 +388,9 @@ public class StripePaymentService : IPaymentService { // Finalize the invoice (from Draft) w/o auto-advance so we // can attempt payment manually. - invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new InvoiceFinalizeOptions - { - AutoAdvance = false, - }); - var invoicePayOptions = new InvoicePayOptions - { - PaymentMethod = cardPaymentMethodId, - }; + invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, + new InvoiceFinalizeOptions { AutoAdvance = false, }); + var invoicePayOptions = new InvoicePayOptions { PaymentMethod = cardPaymentMethodId, }; if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false) { invoicePayOptions.PaidOutOfBand = true; @@ -403,13 +405,15 @@ public class StripePaymentService : IPaymentService SubmitForSettlement = true, PayPal = new Braintree.TransactionOptionsPayPalRequest { - CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id},{subscriber.BraintreeCloudRegionField()}:{_globalSettings.BaseServiceUri.CloudRegion}" + CustomField = + $"{subscriber.BraintreeIdField()}:{subscriber.Id},{subscriber.BraintreeCloudRegionField()}:{_globalSettings.BaseServiceUri.CloudRegion}" } }, CustomFields = new Dictionary { [subscriber.BraintreeIdField()] = subscriber.Id.ToString(), - [subscriber.BraintreeCloudRegionField()] = _globalSettings.BaseServiceUri.CloudRegion + [subscriber.BraintreeCloudRegionField()] = + _globalSettings.BaseServiceUri.CloudRegion } }); @@ -442,9 +446,9 @@ public class StripePaymentService : IPaymentService { // SCA required, get intent client secret var invoiceGetOptions = new InvoiceGetOptions(); - invoiceGetOptions.AddExpand("payment_intent"); + invoiceGetOptions.AddExpand("confirmation_secret"); invoice = await _stripeAdapter.InvoiceGetAsync(invoice.Id, invoiceGetOptions); - paymentIntentClientSecret = invoice?.PaymentIntent?.ClientSecret; + paymentIntentClientSecret = invoice?.ConfirmationSecret?.ClientSecret; } else { @@ -458,6 +462,7 @@ public class StripePaymentService : IPaymentService { await _btGateway.Transaction.RefundAsync(braintreeTransaction.Id); } + if (invoice != null) { if (invoice.Status == "paid") @@ -479,10 +484,8 @@ public class StripePaymentService : IPaymentService // Assumption: Customer balance should now be $0, otherwise payment would not have failed. if (customer.Balance == 0) { - await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions - { - Balance = invoice.StartingBalance - }); + await _stripeAdapter.CustomerUpdateAsync(customer.Id, + new CustomerUpdateOptions { Balance = invoice.StartingBalance }); } } } @@ -496,6 +499,7 @@ public class StripePaymentService : IPaymentService // Let the caller perform any subscription change cleanup throw; } + return paymentIntentClientSecret; } @@ -526,10 +530,10 @@ public class StripePaymentService : IPaymentService try { - var canceledSub = endOfPeriod ? - await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, - new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) : - await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions()); + var canceledSub = endOfPeriod + ? await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, + new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) + : await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions()); if (!canceledSub.CanceledAt.HasValue) { throw new GatewayException("Unable to cancel subscription."); @@ -580,7 +584,7 @@ public class StripePaymentService : IPaymentService { Customer customer = null; var customerExists = subscriber.Gateway == GatewayType.Stripe && - !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId); + !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId); if (customerExists) { customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId); @@ -595,10 +599,10 @@ public class StripePaymentService : IPaymentService subscriber.Gateway = GatewayType.Stripe; subscriber.GatewayCustomerId = customer.Id; } - await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions - { - Balance = customer.Balance - (long)(creditAmount * 100) - }); + + await _stripeAdapter.CustomerUpdateAsync(customer.Id, + new CustomerUpdateOptions { Balance = customer.Balance - (long)(creditAmount * 100) }); + return !customerExists; } @@ -630,50 +634,45 @@ public class StripePaymentService : IPaymentService { var subscriptionInfo = new SubscriptionInfo(); - if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) - { - var customerGetOptions = new CustomerGetOptions(); - customerGetOptions.AddExpand("discount.coupon.applies_to"); - var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions); - - if (customer.Discount != null) - { - subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount); - } - } - - if (string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) + if (string.IsNullOrEmpty(subscriber.GatewaySubscriptionId)) { return subscriptionInfo; } - var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, new SubscriptionGetOptions + var subscription = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, + new SubscriptionGetOptions { Expand = ["customer", "discounts", "test_clock"] }); + + subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(subscription); + + var discount = subscription.Customer.Discount ?? subscription.Discounts.FirstOrDefault(); + + if (discount != null) { - Expand = ["test_clock"] - }); - - if (sub != null) - { - subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub); - - var (suspensionDate, unpaidPeriodEndDate) = await GetSuspensionDateAsync(sub); - - if (suspensionDate.HasValue && unpaidPeriodEndDate.HasValue) - { - subscriptionInfo.Subscription.SuspensionDate = suspensionDate; - subscriptionInfo.Subscription.UnpaidPeriodEndDate = unpaidPeriodEndDate; - } + subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(discount); } - if (sub is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) + var (suspensionDate, unpaidPeriodEndDate) = await GetSuspensionDateAsync(subscription); + + if (suspensionDate.HasValue && unpaidPeriodEndDate.HasValue) + { + subscriptionInfo.Subscription.SuspensionDate = suspensionDate; + subscriptionInfo.Subscription.UnpaidPeriodEndDate = unpaidPeriodEndDate; + } + + if (subscription is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) { return subscriptionInfo; } try { - var upcomingInvoiceOptions = new UpcomingInvoiceOptions { Customer = subscriber.GatewayCustomerId }; - var upcomingInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions); + var invoiceCreatePreviewOptions = new InvoiceCreatePreviewOptions + { + Customer = subscriber.GatewayCustomerId, + Subscription = subscriber.GatewaySubscriptionId + }; + + var upcomingInvoice = await _stripeAdapter.InvoiceCreatePreviewAsync(invoiceCreatePreviewOptions); if (upcomingInvoice != null) { @@ -682,7 +681,12 @@ public class StripePaymentService : IPaymentService } catch (StripeException ex) { - _logger.LogWarning(ex, "Encountered an unexpected Stripe error"); + _logger.LogWarning( + ex, + "Failed to retrieve upcoming invoice for customer {CustomerId}, subscription {SubscriptionId}. Error Code: {ErrorCode}", + subscriber.GatewayCustomerId, + subscriber.GatewaySubscriptionId, + ex.StripeError?.Code); } return subscriptionInfo; @@ -788,7 +792,11 @@ public class StripePaymentService : IPaymentService if (taxInfo.TaxIdType == StripeConstants.TaxIdType.SpanishNIF) { await _stripeAdapter.TaxIdCreateAsync(customer.Id, - new TaxIdCreateOptions { Type = StripeConstants.TaxIdType.EUVAT, Value = $"ES{taxInfo.TaxIdNumber}" }); + new TaxIdCreateOptions + { + Type = StripeConstants.TaxIdType.EUVAT, + Value = $"ES{taxInfo.TaxIdNumber}" + }); } } catch (StripeException e) @@ -829,7 +837,8 @@ public class StripePaymentService : IPaymentService await HasSecretsManagerStandaloneAsync(gatewayCustomerId: organization.GatewayCustomerId, organizationHasSecretsManager: organization.UseSecretsManager); - private async Task HasSecretsManagerStandaloneAsync(string gatewayCustomerId, bool organizationHasSecretsManager) + private async Task HasSecretsManagerStandaloneAsync(string gatewayCustomerId, + bool organizationHasSecretsManager) { if (string.IsNullOrEmpty(gatewayCustomerId)) { @@ -894,26 +903,14 @@ public class StripePaymentService : IPaymentService { var options = new InvoiceCreatePreviewOptions { - AutomaticTax = new InvoiceAutomaticTaxOptions - { - Enabled = true, - }, + AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true, }, Currency = "usd", SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = [ - new() - { - Quantity = 1, - Plan = StripeConstants.Prices.PremiumAnnually - }, - - new() - { - Quantity = parameters.PasswordManager.AdditionalStorage, - Plan = "storage-gb-annually" - } + new InvoiceSubscriptionDetailsItemOptions { Quantity = 1, Plan = StripeConstants.Prices.PremiumAnnually }, + new InvoiceSubscriptionDetailsItemOptions { Quantity = parameters.PasswordManager.AdditionalStorage, Plan = StripeConstants.Prices.StoragePlanPersonal } ] }, CustomerDetails = new InvoiceCustomerDetailsOptions @@ -940,12 +937,9 @@ public class StripePaymentService : IPaymentService throw new BadRequestException("billingPreviewInvalidTaxIdError"); } - options.CustomerDetails.TaxIds = [ - new InvoiceCustomerDetailsTaxIdOptions - { - Type = taxIdType, - Value = parameters.TaxInformation.TaxId - } + options.CustomerDetails.TaxIds = + [ + new InvoiceCustomerDetailsTaxIdOptions { Type = taxIdType, Value = parameters.TaxInformation.TaxId } ]; if (taxIdType == StripeConstants.TaxIdType.SpanishNIF) @@ -964,7 +958,7 @@ public class StripePaymentService : IPaymentService if (gatewayCustomer.Discount != null) { - options.Coupon = gatewayCustomer.Discount.Coupon.Id; + options.Discounts = [new InvoiceDiscountOptions { Coupon = gatewayCustomer.Discount.Coupon.Id }]; } } @@ -972,24 +966,31 @@ public class StripePaymentService : IPaymentService { var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId); - if (gatewaySubscription?.Discount != null) + if (gatewaySubscription?.Discounts is { Count: > 0 }) { - options.Coupon ??= gatewaySubscription.Discount.Coupon.Id; + options.Discounts = gatewaySubscription.Discounts.Select(x => new InvoiceDiscountOptions { Coupon = x.Coupon.Id }).ToList(); } } + if (options.Discounts is { Count: > 0 }) + { + options.Discounts = options.Discounts.DistinctBy(invoiceDiscountOptions => invoiceDiscountOptions.Coupon).ToList(); + } + try { var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options); - var effectiveTaxRate = invoice.Tax != null && invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0 - ? invoice.Tax.Value.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor() + var tax = invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount); + + var effectiveTaxRate = invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0 + ? tax.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor() : 0M; var result = new PreviewInvoiceResponseModel( effectiveTaxRate, invoice.TotalExcludingTax.ToMajor() ?? 0, - invoice.Tax.ToMajor() ?? 0, + tax.ToMajor(), invoice.Total.ToMajor()); return result; } @@ -1003,7 +1004,8 @@ public class StripePaymentService : IPaymentService parameters.TaxInformation.Country); throw new BadRequestException("billingPreviewInvalidTaxIdError"); default: - _logger.LogError(e, "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.", + _logger.LogError(e, + "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.", parameters.TaxInformation.TaxId, parameters.TaxInformation.Country); throw new BadRequestException("billingPreviewInvoiceError"); @@ -1101,12 +1103,9 @@ public class StripePaymentService : IPaymentService throw new BadRequestException("billingTaxIdTypeInferenceError"); } - options.CustomerDetails.TaxIds = [ - new InvoiceCustomerDetailsTaxIdOptions - { - Type = taxIdType, - Value = parameters.TaxInformation.TaxId - } + options.CustomerDetails.TaxIds = + [ + new InvoiceCustomerDetailsTaxIdOptions { Type = taxIdType, Value = parameters.TaxInformation.TaxId } ]; if (taxIdType == StripeConstants.TaxIdType.SpanishNIF) @@ -1127,7 +1126,10 @@ public class StripePaymentService : IPaymentService if (gatewayCustomer.Discount != null) { - options.Coupon = gatewayCustomer.Discount.Coupon.Id; + options.Discounts = + [ + new InvoiceDiscountOptions { Coupon = gatewayCustomer.Discount.Coupon.Id } + ]; } } @@ -1135,9 +1137,10 @@ public class StripePaymentService : IPaymentService { var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId); - if (gatewaySubscription?.Discount != null) + if (gatewaySubscription?.Discounts != null) { - options.Coupon ??= gatewaySubscription.Discount.Coupon.Id; + options.Discounts = gatewaySubscription.Discounts + .Select(discount => new InvoiceDiscountOptions { Coupon = discount.Coupon.Id }).ToList(); } } @@ -1152,14 +1155,16 @@ public class StripePaymentService : IPaymentService { var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options); - var effectiveTaxRate = invoice.Tax != null && invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0 - ? invoice.Tax.Value.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor() + var tax = invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount); + + var effectiveTaxRate = invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0 + ? tax.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor() : 0M; var result = new PreviewInvoiceResponseModel( effectiveTaxRate, invoice.TotalExcludingTax.ToMajor() ?? 0, - invoice.Tax.ToMajor() ?? 0, + tax.ToMajor(), invoice.Total.ToMajor()); return result; } @@ -1173,7 +1178,8 @@ public class StripePaymentService : IPaymentService parameters.TaxInformation.Country); throw new BadRequestException("billingPreviewInvalidTaxIdError"); default: - _logger.LogError(e, "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.", + _logger.LogError(e, + "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.", parameters.TaxInformation.TaxId, parameters.TaxInformation.Country); throw new BadRequestException("billingPreviewInvoiceError"); @@ -1207,7 +1213,9 @@ public class StripePaymentService : IPaymentService braintreeCustomer.DefaultPaymentMethod); } } - catch (Braintree.Exceptions.NotFoundException) { } + catch (Braintree.Exceptions.NotFoundException) + { + } } if (customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card") @@ -1246,12 +1254,15 @@ public class StripePaymentService : IPaymentService { customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId, options); } - catch (StripeException) { } + catch (StripeException) + { + } return customer; } - private async Task> GetBillingTransactionsAsync(ISubscriber subscriber, int? limit = null) + private async Task> GetBillingTransactionsAsync( + ISubscriber subscriber, int? limit = null) { var transactions = subscriber switch { diff --git a/src/Core/Utilities/LoggingExceptionHandlerFilterAttribute.cs b/src/Core/Utilities/LoggingExceptionHandlerFilterAttribute.cs index 6709bbb271..300c30641e 100644 --- a/src/Core/Utilities/LoggingExceptionHandlerFilterAttribute.cs +++ b/src/Core/Utilities/LoggingExceptionHandlerFilterAttribute.cs @@ -17,6 +17,6 @@ public class LoggingExceptionHandlerFilterAttribute : ExceptionFilterAttribute var logger = context.HttpContext.RequestServices .GetRequiredService>(); - logger.LogError(0, exception, exception.Message); + logger.LogError(0, exception, "Unhandled exception"); } } diff --git a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs index e57ed1c85f..b976775aca 100644 --- a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs @@ -14,6 +14,8 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.KeyManagement.Models.Api.Response; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Api; using Bit.Core.Models.Api.Response; using Bit.Core.Repositories; @@ -45,6 +47,7 @@ public abstract class BaseRequestValidator where T : class protected IUserService _userService { get; } protected IUserDecryptionOptionsBuilder UserDecryptionOptionsBuilder { get; } protected IPolicyRequirementQuery PolicyRequirementQuery { get; } + protected IUserAccountKeysQuery _accountKeysQuery { get; } public BaseRequestValidator( UserManager userManager, @@ -63,7 +66,8 @@ public abstract class BaseRequestValidator where T : class IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IPolicyRequirementQuery policyRequirementQuery, IAuthRequestRepository authRequestRepository, - IMailService mailService + IMailService mailService, + IUserAccountKeysQuery userAccountKeysQuery ) { _userManager = userManager; @@ -83,6 +87,7 @@ public abstract class BaseRequestValidator where T : class PolicyRequirementQuery = policyRequirementQuery; _authRequestRepository = authRequestRepository; _mailService = mailService; + _accountKeysQuery = userAccountKeysQuery; } protected async Task ValidateAsync(T context, ValidatedTokenRequest request, @@ -263,8 +268,7 @@ public abstract class BaseRequestValidator where T : class if (_globalSettings.SelfHosted) { _logger.LogWarning(Constants.BypassFiltersEventId, - string.Format("Failed login attempt{0}{1}", twoFactorRequest ? ", 2FA invalid." : ".", - $" {CurrentContext.IpAddress}")); + "Failed login attempt. Is2FARequest: {Is2FARequest} IpAddress: {IpAddress}", twoFactorRequest, CurrentContext.IpAddress); } await Task.Delay(2000); // Delay for brute force. @@ -294,7 +298,7 @@ public abstract class BaseRequestValidator where T : class formattedMessage = "Failed login attempt."; break; } - _logger.LogWarning(Constants.BypassFiltersEventId, formattedMessage); + _logger.LogWarning(Constants.BypassFiltersEventId, "{FailedLoginMessage}", formattedMessage); } await Task.Delay(2000); // Delay for brute force. } @@ -439,6 +443,8 @@ public abstract class BaseRequestValidator where T : class if (!string.IsNullOrWhiteSpace(user.PrivateKey)) { customResponse.Add("PrivateKey", user.PrivateKey); + var accountKeys = await _accountKeysQuery.Run(user); + customResponse.Add("AccountKeys", new PrivateKeysResponseModel(accountKeys)); } if (!string.IsNullOrWhiteSpace(user.Key)) diff --git a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs index 1495973b80..64156ea5f3 100644 --- a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs @@ -8,6 +8,7 @@ using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.Services; @@ -47,7 +48,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator endpoints.MapDefaultControllerRoute()); // Log startup - logger.LogInformation(Constants.BypassFiltersEventId, globalSettings.ProjectName + " started."); + logger.LogInformation(Constants.BypassFiltersEventId, "{Project} started.", globalSettings.ProjectName); } } diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs index 5f389ae56d..dc4fc74ff8 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -15,8 +15,6 @@ using Dapper; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; -#nullable enable - namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationUserRepository : Repository, IOrganizationUserRepository @@ -672,4 +670,20 @@ public class OrganizationUserRepository : Repository, IO }, commandType: CommandType.StoredProcedure); } + + public async Task ConfirmOrganizationUserAsync(OrganizationUser organizationUser) + { + await using var connection = new SqlConnection(_marsConnectionString); + + var rowCount = await connection.ExecuteScalarAsync( + $"[{Schema}].[OrganizationUser_ConfirmById]", + new + { + organizationUser.Id, + organizationUser.UserId, + RevisionDate = DateTime.UtcNow.Date + }); + + return rowCount > 0; + } } diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 35fc094973..445ff77109 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -71,6 +71,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/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs b/src/Infrastructure.Dapper/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs new file mode 100644 index 0000000000..5dcc2943b8 --- /dev/null +++ b/src/Infrastructure.Dapper/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs @@ -0,0 +1,79 @@ +using System.Data; +using Bit.Core.KeyManagement.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.KeyManagement.Repositories; + +public class UserSignatureKeyPairRepository : Repository, IUserSignatureKeyPairRepository +{ + public UserSignatureKeyPairRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public UserSignatureKeyPairRepository(string connectionString, string readOnlyConnectionString) : base( + connectionString, readOnlyConnectionString) + { + } + + public async Task GetByUserIdAsync(Guid userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return (await connection.QuerySingleOrDefaultAsync( + "[dbo].[UserSignatureKeyPair_ReadByUserId]", + new + { + UserId = userId + }, + commandType: CommandType.StoredProcedure))?.ToSignatureKeyPairData(); + } + } + + public UpdateEncryptedDataForKeyRotation SetUserSignatureKeyPair(Guid userId, SignatureKeyPairData signingKeys) + { + return async (SqlConnection connection, SqlTransaction transaction) => + { + await connection.QueryAsync( + "[dbo].[UserSignatureKeyPair_SetForRotation]", + new + { + Id = CoreHelpers.GenerateComb(), + UserId = userId, + SignatureAlgorithm = (byte)signingKeys.SignatureAlgorithm, + SigningKey = signingKeys.WrappedSigningKey, + VerifyingKey = signingKeys.VerifyingKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow + }, + commandType: CommandType.StoredProcedure, + transaction: transaction); + }; + } + + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SignatureKeyPairData signingKeys) + { + return async (SqlConnection connection, SqlTransaction transaction) => + { + await connection.QueryAsync( + "[dbo].[UserSignatureKeyPair_UpdateForRotation]", + new + { + UserId = grantorId, + SignatureAlgorithm = (byte)signingKeys.SignatureAlgorithm, + SigningKey = signingKeys.WrappedSigningKey, + VerifyingKey = signingKeys.VerifyingKey, + RevisionDate = DateTime.UtcNow + }, + commandType: CommandType.StoredProcedure, + transaction: transaction); + }; + } +} diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs index 200c4aa308..2238bfca76 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs @@ -112,7 +112,8 @@ public class OrganizationRepository : Repository GetOccupiedSeatCountByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs index fae0598c1c..b871ec44bf 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -942,4 +942,24 @@ public class OrganizationUserRepository : Repository ConfirmOrganizationUserAsync(Core.Entities.OrganizationUser organizationUser) + { + using var scope = ServiceScopeFactory.CreateScope(); + await using var dbContext = GetDatabaseContext(scope); + + var result = await dbContext.OrganizationUsers + .Where(ou => ou.Id == organizationUser.Id && ou.Status == OrganizationUserStatusType.Accepted) + .ExecuteUpdateAsync(x => + x.SetProperty(y => y.Status, OrganizationUserStatusType.Confirmed)); + + if (result <= 0) + { + return false; + } + + await dbContext.UserBumpAccountRevisionDateByOrganizationUserIdAsync(organizationUser.Id); + return true; + + } } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs index 3689ae4d18..f7f3e70d33 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs @@ -52,6 +52,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure.EntityFramework/KeyManagement/Configurations/UserSignatureKeyPairEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/KeyManagement/Configurations/UserSignatureKeyPairEntityTypeConfiguration.cs new file mode 100644 index 0000000000..aa10a73a88 --- /dev/null +++ b/src/Infrastructure.EntityFramework/KeyManagement/Configurations/UserSignatureKeyPairEntityTypeConfiguration.cs @@ -0,0 +1,22 @@ +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Configurations; + +public class UserSignatureKeyPairEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(s => s.Id) + .ValueGeneratedNever(); + + builder + .HasIndex(s => s.UserId) + .IsUnique() + .IsClustered(false); + + builder.ToTable(nameof(UserSignatureKeyPair)); + } +} diff --git a/src/Infrastructure.EntityFramework/KeyManagement/Models/UserSignatureKeyPair.cs b/src/Infrastructure.EntityFramework/KeyManagement/Models/UserSignatureKeyPair.cs new file mode 100644 index 0000000000..b2bd8a1345 --- /dev/null +++ b/src/Infrastructure.EntityFramework/KeyManagement/Models/UserSignatureKeyPair.cs @@ -0,0 +1,19 @@ +// FIXME: Update this file to be null safe and then delete the line below +#nullable disable + +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class UserSignatureKeyPair : Core.KeyManagement.Entities.UserSignatureKeyPair +{ + public virtual User User { get; set; } +} + +public class UserSignatureKeyPairMapperProfile : Profile +{ + public UserSignatureKeyPairMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs b/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs new file mode 100644 index 0000000000..04f055501d --- /dev/null +++ b/src/Infrastructure.EntityFramework/KeyManagement/Repositories/UserSignatureKeyPairRepository.cs @@ -0,0 +1,66 @@ + +using AutoMapper; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; +using Bit.Core.KeyManagement.UserKey; +using Bit.Core.Utilities; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.KeyManagement.Repositories; + +public class UserSignatureKeyPairRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : Repository(serviceScopeFactory, mapper, context => context.UserSignatureKeyPairs), IUserSignatureKeyPairRepository +{ + public async Task GetByUserIdAsync(Guid userId) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var signingKeys = await dbContext.UserSignatureKeyPairs.FirstOrDefaultAsync(x => x.UserId == userId); + if (signingKeys == null) + { + return null; + } + + return signingKeys.ToSignatureKeyPairData(); + } + + public UpdateEncryptedDataForKeyRotation SetUserSignatureKeyPair(Guid userId, SignatureKeyPairData signingKeys) + { + return async (_, _) => + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var entity = new Models.UserSignatureKeyPair + { + Id = CoreHelpers.GenerateComb(), + UserId = userId, + SignatureAlgorithm = signingKeys.SignatureAlgorithm, + SigningKey = signingKeys.WrappedSigningKey, + VerifyingKey = signingKeys.VerifyingKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + }; + await dbContext.UserSignatureKeyPairs.AddAsync(entity); + await dbContext.SaveChangesAsync(); + }; + } + + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid grantorId, SignatureKeyPairData signingKeys) + { + return async (_, _) => + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.UserSignatureKeyPairs.FirstOrDefaultAsync(x => x.UserId == grantorId); + if (entity != null) + { + entity.SignatureAlgorithm = signingKeys.SignatureAlgorithm; + entity.SigningKey = signingKeys.WrappedSigningKey; + entity.VerifyingKey = signingKeys.VerifyingKey; + entity.RevisionDate = DateTime.UtcNow; + await dbContext.SaveChangesAsync(); + } + }; + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index ef238b5d2c..d3ccbb62d4 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -63,6 +63,7 @@ public class DatabaseContext : DbContext public DbSet Policies { get; set; } public DbSet Providers { get; set; } public DbSet Secret { get; set; } + public DbSet SecretVersion { get; set; } public DbSet ServiceAccount { get; set; } public DbSet Project { get; set; } public DbSet ProviderUsers { get; set; } @@ -73,6 +74,7 @@ public class DatabaseContext : DbContext public DbSet TaxRates { get; set; } public DbSet Transactions { get; set; } public DbSet Users { get; set; } + public DbSet UserSignatureKeyPairs { get; set; } public DbSet AuthRequests { get; set; } public DbSet OrganizationDomains { get; set; } public DbSet WebAuthnCredentials { get; set; } diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Configurations/SecretVersionEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/SecretsManager/Configurations/SecretVersionEntityTypeConfiguration.cs new file mode 100644 index 0000000000..069c7e2450 --- /dev/null +++ b/src/Infrastructure.EntityFramework/SecretsManager/Configurations/SecretVersionEntityTypeConfiguration.cs @@ -0,0 +1,42 @@ +using Bit.Infrastructure.EntityFramework.SecretsManager.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.SecretsManager.Configurations; + +public class SecretVersionEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(sv => sv.Id) + .ValueGeneratedNever(); + + builder.HasKey(sv => sv.Id) + .IsClustered(); + + builder.Property(sv => sv.Value) + .IsRequired(); + + builder.Property(sv => sv.VersionDate) + .IsRequired(); + + builder.HasOne(sv => sv.EditorServiceAccount) + .WithMany() + .HasForeignKey(sv => sv.EditorServiceAccountId) + .OnDelete(DeleteBehavior.SetNull); + + builder.HasOne(sv => sv.EditorOrganizationUser) + .WithMany() + .HasForeignKey(sv => sv.EditorOrganizationUserId) + .OnDelete(DeleteBehavior.SetNull); + + builder.HasIndex(sv => sv.SecretId) + .HasDatabaseName("IX_SecretVersion_SecretId"); + + builder.HasIndex(sv => sv.EditorServiceAccountId) + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + builder.HasIndex(sv => sv.EditorOrganizationUserId) + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + } +} diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Models/Secret.cs b/src/Infrastructure.EntityFramework/SecretsManager/Models/Secret.cs index 5992f32135..09d8c389df 100644 --- a/src/Infrastructure.EntityFramework/SecretsManager/Models/Secret.cs +++ b/src/Infrastructure.EntityFramework/SecretsManager/Models/Secret.cs @@ -13,6 +13,7 @@ public class Secret : Core.SecretsManager.Entities.Secret public virtual ICollection UserAccessPolicies { get; set; } public virtual ICollection GroupAccessPolicies { get; set; } public virtual ICollection ServiceAccountAccessPolicies { get; set; } + public virtual ICollection SecretVersions { get; set; } } public class SecretMapperProfile : Profile diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Models/SecretVersion.cs b/src/Infrastructure.EntityFramework/SecretsManager/Models/SecretVersion.cs new file mode 100644 index 0000000000..d4a364ab0f --- /dev/null +++ b/src/Infrastructure.EntityFramework/SecretsManager/Models/SecretVersion.cs @@ -0,0 +1,24 @@ +#nullable enable + +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.SecretsManager.Models; + +public class SecretVersion : Core.SecretsManager.Entities.SecretVersion +{ + public Secret? Secret { get; set; } + + public ServiceAccount? EditorServiceAccount { get; set; } + + public Bit.Infrastructure.EntityFramework.Models.OrganizationUser? EditorOrganizationUser { get; set; } +} + +public class SecretVersionMapperProfile : Profile +{ + public SecretVersionMapperProfile() + { + CreateMap() + .PreserveReferences() + .ReverseMap(); + } +} diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index 69d5bdc958..0fea72edc3 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -64,7 +64,7 @@ public static class HubHelpers case PushType.SyncSettings: case PushType.LogOut: var userNotification = - JsonSerializer.Deserialize>( + JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); await hubContext.Clients.User(userNotification.Payload.UserId.ToString()) .SendAsync(_receiveMessageMethod, userNotification, cancellationToken); diff --git a/src/SharedWeb/Utilities/ExceptionHandlerFilterAttribute.cs b/src/SharedWeb/Utilities/ExceptionHandlerFilterAttribute.cs index 332aa6838c..aba1a6a8dc 100644 --- a/src/SharedWeb/Utilities/ExceptionHandlerFilterAttribute.cs +++ b/src/SharedWeb/Utilities/ExceptionHandlerFilterAttribute.cs @@ -75,7 +75,7 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute else { var logger = context.HttpContext.RequestServices.GetRequiredService>(); - logger.LogError(0, exception, exception.Message); + logger.LogError(0, exception, "Unhandled exception"); errorMessage = "An unhandled server error has occurred."; context.HttpContext.Response.StatusCode = 500; } diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 1a7530321e..0622c5cbb2 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -17,7 +17,4 @@ 71502 - - - diff --git a/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_ReadByUserId.sql b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_ReadByUserId.sql new file mode 100644 index 0000000000..8bfa0156af --- /dev/null +++ b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_ReadByUserId.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[UserSignatureKeyPair_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON; + + SELECT + * + FROM + [dbo].[UserSignatureKeyPairView] + WHERE + [UserId] = @UserId; +END diff --git a/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_SetForRotation.sql b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_SetForRotation.sql new file mode 100644 index 0000000000..6ee33e2a40 --- /dev/null +++ b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_SetForRotation.sql @@ -0,0 +1,33 @@ +CREATE PROCEDURE [dbo].[UserSignatureKeyPair_SetForRotation] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @SignatureAlgorithm TINYINT, + @SigningKey VARCHAR(MAX), + @VerifyingKey VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON; + + INSERT INTO [dbo].[UserSignatureKeyPair] + ( + [Id], + [UserId], + [SignatureAlgorithm], + [SigningKey], + [VerifyingKey], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @UserId, + @SignatureAlgorithm, + @SigningKey, + @VerifyingKey, + @CreationDate, + @RevisionDate + ) +END diff --git a/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_UpdateForRotation.sql b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_UpdateForRotation.sql new file mode 100644 index 0000000000..4f673019fc --- /dev/null +++ b/src/Sql/dbo/KeyManagement/Stored Procedures/UserSignatureKeyPair_UpdateForRotation.sql @@ -0,0 +1,19 @@ +CREATE PROCEDURE [dbo].[UserSignatureKeyPair_UpdateForRotation] + @UserId UNIQUEIDENTIFIER, + @SignatureAlgorithm TINYINT, + @SigningKey VARCHAR(MAX), + @VerifyingKey VARCHAR(MAX), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON; + UPDATE + [dbo].[UserSignatureKeyPair] + SET + [SignatureAlgorithm] = @SignatureAlgorithm, + [SigningKey] = @SigningKey, + [VerifyingKey] = @VerifyingKey, + [RevisionDate] = @RevisionDate + WHERE + [UserId] = @UserId; +END diff --git a/src/Sql/dbo/KeyManagement/Tables/UserSignatureKeyPair.sql b/src/Sql/dbo/KeyManagement/Tables/UserSignatureKeyPair.sql new file mode 100644 index 0000000000..94d4e48a0b --- /dev/null +++ b/src/Sql/dbo/KeyManagement/Tables/UserSignatureKeyPair.sql @@ -0,0 +1,16 @@ +CREATE TABLE [dbo].[UserSignatureKeyPair] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [SignatureAlgorithm] TINYINT NOT NULL, + [SigningKey] VARCHAR(MAX) NOT NULL, + [VerifyingKey] VARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_UserSignatureKeyPair] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_UserSignatureKeyPair_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE +); +GO + +CREATE UNIQUE NONCLUSTERED INDEX [IX_UserSignatureKeyPair_UserId] + ON [dbo].[UserSignatureKeyPair]([UserId] ASC); +GO diff --git a/src/Sql/dbo/KeyManagement/Views/UserSignatureKeyPairView.sql b/src/Sql/dbo/KeyManagement/Views/UserSignatureKeyPairView.sql new file mode 100644 index 0000000000..959305a3e7 --- /dev/null +++ b/src/Sql/dbo/KeyManagement/Views/UserSignatureKeyPairView.sql @@ -0,0 +1,6 @@ +CREATE VIEW [dbo].[UserSignatureKeyPairView] +AS +SELECT + * +FROM + [dbo].[UserSignatureKeyPair] diff --git a/src/Sql/dbo/SecretsManager/Tables/SecretVersion.sql b/src/Sql/dbo/SecretsManager/Tables/SecretVersion.sql new file mode 100644 index 0000000000..31ab443f56 --- /dev/null +++ b/src/Sql/dbo/SecretsManager/Tables/SecretVersion.sql @@ -0,0 +1,27 @@ +CREATE TABLE [dbo].[SecretVersion] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [SecretId] UNIQUEIDENTIFIER NOT NULL, + [Value] NVARCHAR (MAX) NOT NULL, + [VersionDate] DATETIME2 (7) NOT NULL, + [EditorServiceAccountId] UNIQUEIDENTIFIER NULL, + [EditorOrganizationUserId] UNIQUEIDENTIFIER NULL, + CONSTRAINT [PK_SecretVersion] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_SecretVersion_OrganizationUser] FOREIGN KEY ([EditorOrganizationUserId]) REFERENCES [dbo].[OrganizationUser] ([Id]) ON DELETE SET NULL, + CONSTRAINT [FK_SecretVersion_Secret] FOREIGN KEY ([SecretId]) REFERENCES [dbo].[Secret] ([Id]) ON DELETE CASCADE, + CONSTRAINT [FK_SecretVersion_ServiceAccount] FOREIGN KEY ([EditorServiceAccountId]) REFERENCES [dbo].[ServiceAccount] ([Id]) ON DELETE SET NULL +); + +GO +CREATE NONCLUSTERED INDEX [IX_SecretVersion_SecretId] + ON [dbo].[SecretVersion]([SecretId] ASC); + +GO +CREATE NONCLUSTERED INDEX [IX_SecretVersion_EditorServiceAccountId] + ON [dbo].[SecretVersion]([EditorServiceAccountId] ASC) + WHERE [EditorServiceAccountId] IS NOT NULL; + +GO +CREATE NONCLUSTERED INDEX [IX_SecretVersion_EditorOrganizationUserId] + ON [dbo].[SecretVersion]([EditorOrganizationUserId] ASC) + WHERE [EditorOrganizationUserId] IS NOT NULL; +GO \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ConfirmById.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ConfirmById.sql new file mode 100644 index 0000000000..004f1c93eb --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ConfirmById.sql @@ -0,0 +1,28 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_ConfirmById] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + DECLARE @RowCount INT; + + UPDATE + [dbo].[OrganizationUser] + SET + [Status] = 2, -- Set to Confirmed + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id + AND [Status] = 1 -- Only update if status is Accepted + + SET @RowCount = @@ROWCOUNT; + + IF @RowCount > 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END + + SELECT @RowCount; +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index 295ebb51a8..e37fa0e940 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -58,7 +58,8 @@ CREATE PROCEDURE [dbo].[Organization_Create] @LimitItemDeletion BIT = 0, @UseOrganizationDomains BIT = 0, @UseAdminSponsoredFamilies BIT = 0, - @SyncSeats BIT = 0 + @SyncSeats BIT = 0, + @UseAutomaticUserConfirmation BIT = 0 AS BEGIN SET NOCOUNT ON @@ -124,69 +125,71 @@ BEGIN [LimitItemDeletion], [UseOrganizationDomains], [UseAdminSponsoredFamilies], - [SyncSeats] + [SyncSeats], + [UseAutomaticUserConfirmation] ) VALUES - ( - @Id, - @Identifier, - @Name, - @BusinessName, - @BusinessAddress1, - @BusinessAddress2, - @BusinessAddress3, - @BusinessCountry, - @BusinessTaxNumber, - @BillingEmail, - @Plan, - @PlanType, - @Seats, - @MaxCollections, - @UsePolicies, - @UseSso, - @UseGroups, - @UseDirectory, - @UseEvents, - @UseTotp, - @Use2fa, - @UseApi, - @UseResetPassword, - @SelfHost, - @UsersGetPremium, - @Storage, - @MaxStorageGb, - @Gateway, - @GatewayCustomerId, - @GatewaySubscriptionId, - @ReferenceData, - @Enabled, - @LicenseKey, - @PublicKey, - @PrivateKey, - @TwoFactorProviders, - @ExpirationDate, - @CreationDate, - @RevisionDate, - @OwnersNotifiedOfAutoscaling, - @MaxAutoscaleSeats, - @UseKeyConnector, - @UseScim, - @UseCustomPermissions, - @UseSecretsManager, - @Status, - @UsePasswordManager, - @SmSeats, - @SmServiceAccounts, - @MaxAutoscaleSmSeats, - @MaxAutoscaleSmServiceAccounts, - @SecretsManagerBeta, - @LimitCollectionCreation, - @LimitCollectionDeletion, - @AllowAdminAccessToAllCollectionItems, - @UseRiskInsights, - @LimitItemDeletion, - @UseOrganizationDomains, - @UseAdminSponsoredFamilies, - @SyncSeats - ) + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector, + @UseScim, + @UseCustomPermissions, + @UseSecretsManager, + @Status, + @UsePasswordManager, + @SmSeats, + @SmServiceAccounts, + @MaxAutoscaleSmSeats, + @MaxAutoscaleSmServiceAccounts, + @SecretsManagerBeta, + @LimitCollectionCreation, + @LimitCollectionDeletion, + @AllowAdminAccessToAllCollectionItems, + @UseRiskInsights, + @LimitItemDeletion, + @UseOrganizationDomains, + @UseAdminSponsoredFamilies, + @SyncSeats, + @UseAutomaticUserConfirmation + ); END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql index 6a8ed9e0d0..59226e59db 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql @@ -27,7 +27,8 @@ BEGIN [UseRiskInsights], [LimitItemDeletion], [UseOrganizationDomains], - [UseAdminSponsoredFamilies] + [UseAdminSponsoredFamilies], + [UseAutomaticUserConfirmation] FROM [dbo].[Organization] END diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index d60852bab6..4807c7bb50 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -58,7 +58,8 @@ CREATE PROCEDURE [dbo].[Organization_Update] @LimitItemDeletion BIT = 0, @UseOrganizationDomains BIT = 0, @UseAdminSponsoredFamilies BIT = 0, - @SyncSeats BIT = 0 + @SyncSeats BIT = 0, + @UseAutomaticUserConfirmation BIT = 0 AS BEGIN SET NOCOUNT ON @@ -124,7 +125,8 @@ BEGIN [LimitItemDeletion] = @LimitItemDeletion, [UseOrganizationDomains] = @UseOrganizationDomains, [UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies, - [SyncSeats] = @SyncSeats + [SyncSeats] = @SyncSeats, + [UseAutomaticUserConfirmation] = @UseAutomaticUserConfirmation WHERE - [Id] = @Id + [Id] = @Id; END diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 60d9b5eb32..2573bf1a0a 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -41,7 +41,10 @@ @LastKdfChangeDate DATETIME2(7) = NULL, @LastKeyRotationDate DATETIME2(7) = NULL, @LastEmailChangeDate DATETIME2(7) = NULL, - @VerifyDevices BIT = 1 + @VerifyDevices BIT = 1, + @SecurityState VARCHAR(MAX) = NULL, + @SecurityVersion INT = NULL, + @SignedPublicKey VARCHAR(MAX) = NULL AS BEGIN SET NOCOUNT ON @@ -90,7 +93,10 @@ BEGIN [LastKdfChangeDate], [LastKeyRotationDate], [LastEmailChangeDate], - [VerifyDevices] + [VerifyDevices], + [SecurityState], + [SecurityVersion], + [SignedPublicKey] ) VALUES ( @@ -136,6 +142,9 @@ BEGIN @LastKdfChangeDate, @LastKeyRotationDate, @LastEmailChangeDate, - @VerifyDevices + @VerifyDevices, + @SecurityState, + @SecurityVersion, + @SignedPublicKey ) END diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index 15d04d72f6..5097bc538e 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -41,7 +41,10 @@ @LastKdfChangeDate DATETIME2(7) = NULL, @LastKeyRotationDate DATETIME2(7) = NULL, @LastEmailChangeDate DATETIME2(7) = NULL, - @VerifyDevices BIT = 1 + @VerifyDevices BIT = 1, + @SecurityState VARCHAR(MAX) = NULL, + @SecurityVersion INT = NULL, + @SignedPublicKey VARCHAR(MAX) = NULL AS BEGIN SET NOCOUNT ON @@ -90,7 +93,10 @@ BEGIN [LastKdfChangeDate] = @LastKdfChangeDate, [LastKeyRotationDate] = @LastKeyRotationDate, [LastEmailChangeDate] = @LastEmailChangeDate, - [VerifyDevices] = @VerifyDevices + [VerifyDevices] = @VerifyDevices, + [SecurityState] = @SecurityState, + [SecurityVersion] = @SecurityVersion, + [SignedPublicKey] = @SignedPublicKey WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index 897abef1cf..e1ad6863af 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -59,6 +59,7 @@ CREATE TABLE [dbo].[Organization] ( [UseOrganizationDomains] BIT NOT NULL CONSTRAINT [DF_Organization_UseOrganizationDomains] DEFAULT (0), [UseAdminSponsoredFamilies] BIT NOT NULL CONSTRAINT [DF_Organization_UseAdminSponsoredFamilies] DEFAULT (0), [SyncSeats] BIT NOT NULL CONSTRAINT [DF_Organization_SyncSeats] DEFAULT (0), + [UseAutomaticUserConfirmation] BIT NOT NULL CONSTRAINT [DF_Organization_UseAutomaticUserConfirmation] DEFAULT (0), CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 239ee67f11..dc772ff1a7 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -42,6 +42,9 @@ [LastKeyRotationDate] DATETIME2 (7) NULL, [LastEmailChangeDate] DATETIME2 (7) NULL, [VerifyDevices] BIT DEFAULT ((1)) NOT NULL, + [SecurityState] VARCHAR (MAX) NULL, + [SecurityVersion] INT NULL, + [SignedPublicKey] VARCHAR (MAX) NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql index ba7e765569..a7e1db6e81 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -54,7 +54,8 @@ SELECT O.[LimitItemDeletion], O.[UseAdminSponsoredFamilies], O.[UseOrganizationDomains], - OS.[IsAdminInitiated] + OS.[IsAdminInitiated], + O.[UseAutomaticUserConfirmation] FROM [dbo].[OrganizationUser] OU LEFT JOIN diff --git a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql index bd2485b411..9dc6e561d6 100644 --- a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql @@ -39,7 +39,8 @@ SELECT O.[UseAdminSponsoredFamilies], P.[Type] ProviderType, O.[LimitItemDeletion], - O.[UseOrganizationDomains] + O.[UseOrganizationDomains], + O.[UseAutomaticUserConfirmation] FROM [dbo].[ProviderUser] PU INNER JOIN diff --git a/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index 44ad5088cd..84ef5c7f3d 100644 --- a/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs +++ b/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -1,5 +1,7 @@ using Bit.Admin.AdminConsole.Controllers; using Bit.Admin.AdminConsole.Models; +using Bit.Admin.Enums; +using Bit.Admin.Services; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; @@ -276,5 +278,40 @@ public class OrganizationsControllerTests await providerBillingService.Received(1).ScaleSeats(provider, update.PlanType!.Value, update.Seats!.Value - organization.Seats.Value + organization.Seats.Value); } + [BitAutoData] + [SutProviderCustomize] + [Theory] + public async Task Edit_UseAutomaticUserConfirmation_FullUpdate_SavesFeatureCorrectly( + Organization organization, + SutProvider sutProvider) + { + // Arrange + var update = new OrganizationEditModel + { + PlanType = PlanType.TeamsMonthly, + UseAutomaticUserConfirmation = true + }; + + organization.UseAutomaticUserConfirmation = false; + + sutProvider.GetDependency() + .UserHasPermission(Permission.Org_Plan_Edit) + .Returns(true); + + var organizationRepository = sutProvider.GetDependency(); + + organizationRepository.GetByIdAsync(organization.Id).Returns(organization); + + // Act + _ = await sutProvider.Sut.Edit(organization.Id, update); + + // Assert + await organizationRepository.Received(1).ReplaceAsync(Arg.Is(o => o.Id == organization.Id + && o.UseAutomaticUserConfirmation == true)); + + // Annul + await organizationRepository.DeleteAsync(organization); + } + #endregion } diff --git a/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs b/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs index 4e5a6850e7..09ec5b010f 100644 --- a/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs +++ b/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs @@ -1,31 +1,81 @@ -using System.Net.Http.Headers; +using System.Net; +using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.Helpers; +using Bit.Api.KeyManagement.Models.Requests; using Bit.Api.Models.Response; +using Bit.Core; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Platform.Push; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Identity; +using NSubstitute; using Xunit; namespace Bit.Api.IntegrationTest.Controllers; -public class AccountsControllerTest : IClassFixture +public class AccountsControllerTest : IClassFixture, IAsyncLifetime { - private readonly ApiApplicationFactory _factory; + private static readonly string _masterKeyWrappedUserKey = + "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk="; - public AccountsControllerTest(ApiApplicationFactory factory) => _factory = factory; + private static readonly string _masterPasswordHash = "master_password_hash"; + private static readonly string _newMasterPasswordHash = "new_master_password_hash"; + + private static readonly KdfRequestModel _defaultKdfRequest = + new() { KdfType = KdfType.PBKDF2_SHA256, Iterations = 600_000 }; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + private readonly LoginHelper _loginHelper; + private readonly IUserRepository _userRepository; + private readonly IPushNotificationService _pushNotificationService; + private readonly IFeatureService _featureService; + private readonly IPasswordHasher _passwordHasher; + + private string _ownerEmail = null!; + + public AccountsControllerTest(ApiApplicationFactory factory) + { + _factory = factory; + _factory.SubstituteService(_ => { }); + _factory.SubstituteService(_ => { }); + _client = factory.CreateClient(); + _loginHelper = new LoginHelper(_factory, _client); + _userRepository = _factory.GetService(); + _pushNotificationService = _factory.GetService(); + _featureService = _factory.GetService(); + _passwordHasher = _factory.GetService>(); + } + + public async Task InitializeAsync() + { + _ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_ownerEmail); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } [Fact] public async Task GetAccountsProfile_success() { - var tokens = await _factory.LoginWithNewAccount(); - var client = _factory.CreateClient(); + await _loginHelper.LoginAsync(_ownerEmail); using var message = new HttpRequestMessage(HttpMethod.Get, "/accounts/profile"); - message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); - var response = await client.SendAsync(message); + var response = await _client.SendAsync(message); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadFromJsonAsync(); Assert.NotNull(content); - Assert.Equal("integration-test@bitwarden.com", content.Email); + Assert.Equal(_ownerEmail, content.Email); Assert.NotNull(content.Name); Assert.True(content.EmailVerified); Assert.False(content.Premium); @@ -35,4 +85,354 @@ public class AccountsControllerTest : IClassFixture Assert.NotNull(content.PrivateKey); Assert.NotNull(content.SecurityStamp); } + + [Theory] + [BitAutoData(KdfType.PBKDF2_SHA256, 600001, null, null)] + [BitAutoData(KdfType.Argon2id, 4, 65, 5)] + public async Task PostKdf_ValidRequestLogoutOnKdfChangeFeatureFlagOff_SuccessLogout(KdfType kdf, + int kdfIterations, int? kdfMemory, int? kdfParallelism) + { + var userBeforeKdfChange = await _userRepository.GetByEmailAsync(_ownerEmail); + Assert.NotNull(userBeforeKdfChange); + + _featureService.IsEnabled(FeatureFlagKeys.NoLogoutOnKdfChange).Returns(false); + + await _loginHelper.LoginAsync(_ownerEmail); + + var kdfRequest = new KdfRequestModel + { + KdfType = kdf, + Iterations = kdfIterations, + Memory = kdfMemory, + Parallelism = kdfParallelism, + }; + + var response = await PostKdfWithKdfRequestAsync(kdfRequest); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Validate that the user fields were updated correctly + var user = await _userRepository.GetByEmailAsync(_ownerEmail); + Assert.NotNull(user); + Assert.Equal(kdfRequest.KdfType, user.Kdf); + Assert.Equal(kdfRequest.Iterations, user.KdfIterations); + Assert.Equal(kdfRequest.Memory, user.KdfMemory); + Assert.Equal(kdfRequest.Parallelism, user.KdfParallelism); + Assert.Equal(_masterKeyWrappedUserKey, user.Key); + Assert.NotNull(user.LastKdfChangeDate); + Assert.True(user.LastKdfChangeDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.True(user.RevisionDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.True(user.AccountRevisionDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.NotEqual(userBeforeKdfChange.SecurityStamp, user.SecurityStamp); + Assert.Equal(PasswordVerificationResult.Success, + _passwordHasher.VerifyHashedPassword(user, user.MasterPassword!, _newMasterPasswordHash)); + + // Validate push notification + await _pushNotificationService.Received(1).PushLogOutAsync(user.Id); + } + + [Theory] + [BitAutoData(KdfType.PBKDF2_SHA256, 600001, null, null)] + [BitAutoData(KdfType.Argon2id, 4, 65, 5)] + public async Task PostKdf_ValidRequestLogoutOnKdfChangeFeatureFlagOn_SuccessSyncAndLogoutWithReason(KdfType kdf, + int kdfIterations, int? kdfMemory, int? kdfParallelism) + { + var userBeforeKdfChange = await _userRepository.GetByEmailAsync(_ownerEmail); + Assert.NotNull(userBeforeKdfChange); + + _featureService.IsEnabled(FeatureFlagKeys.NoLogoutOnKdfChange).Returns(true); + + await _loginHelper.LoginAsync(_ownerEmail); + + var kdfRequest = new KdfRequestModel + { + KdfType = kdf, + Iterations = kdfIterations, + Memory = kdfMemory, + Parallelism = kdfParallelism, + }; + + var response = await PostKdfWithKdfRequestAsync(kdfRequest); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Validate that the user fields were updated correctly + var user = await _userRepository.GetByEmailAsync(_ownerEmail); + Assert.NotNull(user); + Assert.Equal(kdfRequest.KdfType, user.Kdf); + Assert.Equal(kdfRequest.Iterations, user.KdfIterations); + Assert.Equal(kdfRequest.Memory, user.KdfMemory); + Assert.Equal(kdfRequest.Parallelism, user.KdfParallelism); + Assert.Equal(_masterKeyWrappedUserKey, user.Key); + Assert.NotNull(user.LastKdfChangeDate); + Assert.True(user.LastKdfChangeDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.True(user.RevisionDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.True(user.AccountRevisionDate > DateTime.UtcNow.AddMinutes(-1)); + Assert.Equal(userBeforeKdfChange.SecurityStamp, user.SecurityStamp); + Assert.Equal(PasswordVerificationResult.Success, + _passwordHasher.VerifyHashedPassword(user, user.MasterPassword!, _newMasterPasswordHash)); + + // Validate push notification + await _pushNotificationService.Received(1) + .PushLogOutAsync(user.Id, false, PushNotificationLogOutReason.KdfChange); + await _pushNotificationService.Received(1).PushSyncSettingsAsync(user.Id); + } + + [Fact] + public async Task PostKdf_Unauthorized_ReturnsUnauthorized() + { + // Don't call LoginAsync to test unauthorized access + + var response = await PostKdfWithKdfRequestAsync(_defaultKdfRequest); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task PostKdf_AuthenticationDataOrUnlockDataNull_BadRequest(bool authenticationDataNull, + bool unlockDataNull) + { + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = authenticationDataNull + ? null + : new MasterPasswordAuthenticationDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = unlockDataNull + ? null + : new MasterPasswordUnlockDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + var response = await PostKdfAsync(authenticationData, unlockData); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("AuthenticationData and UnlockData must be provided.", content); + } + + [Fact] + public async Task PostKdf_InvalidMasterPasswordHash_BadRequest() + { + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + var requestModel = new PasswordRequestModel + { + MasterPasswordHash = "wrong-master-password-hash", + NewMasterPasswordHash = _newMasterPasswordHash, + Key = _masterKeyWrappedUserKey, + AuthenticationData = authenticationData, + UnlockData = unlockData + }; + + using var message = new HttpRequestMessage(HttpMethod.Post, "/accounts/kdf"); + message.Content = JsonContent.Create(requestModel); + var response = await _client.SendAsync(message); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Incorrect password", content); + } + + [Fact] + public async Task PostKdf_ChangedSaltInAuthenticationData_BadRequest() + { + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = "wrong-salt@bitwarden.com" + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + var response = await PostKdfAsync(authenticationData, unlockData); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Invalid master password salt.", content); + } + + [Fact] + public async Task PostKdf_ChangedSaltInUnlockData_BadRequest() + { + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = "wrong-salt@bitwarden.com" + }; + + var response = await PostKdfAsync(authenticationData, unlockData); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Invalid master password salt.", content); + } + + [Fact] + public async Task PostKdf_KdfNotMatching_BadRequest() + { + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = 600_000 }, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = 600_001 }, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + var response = await PostKdfAsync(authenticationData, unlockData); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("KDF settings must be equal for authentication and unlock.", content); + } + + [Theory] + [InlineData(KdfType.PBKDF2_SHA256, 1, null, null)] + [InlineData(KdfType.Argon2id, 4, null, 5)] + [InlineData(KdfType.Argon2id, 4, 65, null)] + public async Task PostKdf_InvalidKdf_BadRequest(KdfType kdf, int kdfIterations, int? kdfMemory, int? kdfParallelism) + { + await _loginHelper.LoginAsync(_ownerEmail); + + var kdfRequest = new KdfRequestModel + { + KdfType = kdf, + Iterations = kdfIterations, + Memory = kdfMemory, + Parallelism = kdfParallelism + }; + + var response = await PostKdfWithKdfRequestAsync(kdfRequest); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("KDF settings are invalid", content); + } + + [Fact] + public async Task PostKdf_InvalidNewMasterPassword_BadRequest() + { + var newMasterPasswordHash = "too-short"; + + await _loginHelper.LoginAsync(_ownerEmail); + + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterPasswordAuthenticationHash = newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = _defaultKdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + var requestModel = new PasswordRequestModel + { + MasterPasswordHash = _masterPasswordHash, + NewMasterPasswordHash = newMasterPasswordHash, + Key = _masterKeyWrappedUserKey, + AuthenticationData = authenticationData, + UnlockData = unlockData + }; + + using var message = new HttpRequestMessage(HttpMethod.Post, "/accounts/kdf"); + message.Content = JsonContent.Create(requestModel); + var response = await _client.SendAsync(message); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Contains("Passwords must be at least", content); + } + + private async Task PostKdfWithKdfRequestAsync(KdfRequestModel kdfRequest) + { + var authenticationData = new MasterPasswordAuthenticationDataRequestModel + { + Kdf = kdfRequest, + MasterPasswordAuthenticationHash = _newMasterPasswordHash, + Salt = _ownerEmail + }; + + var unlockData = new MasterPasswordUnlockDataRequestModel + { + Kdf = kdfRequest, + MasterKeyWrappedUserKey = _masterKeyWrappedUserKey, + Salt = _ownerEmail + }; + + return await PostKdfAsync(authenticationData, unlockData); + } + + private async Task PostKdfAsync( + MasterPasswordAuthenticationDataRequestModel? authenticationDataRequest, + MasterPasswordUnlockDataRequestModel? unlockDataRequest) + { + var requestModel = new PasswordRequestModel + { + MasterPasswordHash = _masterPasswordHash, + NewMasterPasswordHash = _newMasterPasswordHash, + Key = _masterKeyWrappedUserKey, + AuthenticationData = authenticationDataRequest, + UnlockData = unlockDataRequest + }; + + using var message = new HttpRequestMessage(HttpMethod.Post, "/accounts/kdf"); + message.Content = JsonContent.Create(requestModel); + return await _client.SendAsync(message); + } } diff --git a/test/Api.IntegrationTest/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs b/test/Api.IntegrationTest/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs index bf27d7f0d1..1630bc0dc0 100644 --- a/test/Api.IntegrationTest/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs +++ b/test/Api.IntegrationTest/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs @@ -12,6 +12,10 @@ using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.KeyManagement.Entities; +using Bit.Core.KeyManagement.Enums; +using Bit.Core.KeyManagement.Models.Api.Request; +using Bit.Core.KeyManagement.Repositories; using Bit.Core.Repositories; using Bit.Core.Vault.Enums; using Bit.Test.Common.AutoFixture.Attributes; @@ -24,6 +28,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture _passwordHasher; private readonly IOrganizationRepository _organizationRepository; + private readonly IUserSignatureKeyPairRepository _userSignatureKeyPairRepository; private string _ownerEmail = null!; public AccountsKeyManagementControllerTests(ApiApplicationFactory factory) @@ -49,6 +55,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture(); _passwordHasher = _factory.GetService>(); _organizationRepository = _factory.GetService(); + _userSignatureKeyPairRepository = _factory.GetService(); } public async Task InitializeAsync() @@ -200,6 +207,7 @@ public class AccountsKeyManagementControllerTests : IClassFixture(); @@ -48,6 +50,7 @@ public class AccountsControllerTests : IDisposable _twoFactorIsEnabledQuery = Substitute.For(); _tdeOffboardingPasswordCommand = Substitute.For(); _featureService = Substitute.For(); + _userAccountKeysQuery = Substitute.For(); _twoFactorEmailService = Substitute.For(); _changeKdfCommand = Substitute.For(); @@ -61,6 +64,7 @@ public class AccountsControllerTests : IDisposable _tdeOffboardingPasswordCommand, _twoFactorIsEnabledQuery, _featureService, + _userAccountKeysQuery, _twoFactorEmailService, _changeKdfCommand ); @@ -614,6 +618,16 @@ public class AccountsControllerTests : IDisposable await _twoFactorEmailService.Received(1).SendNewDeviceVerificationEmailAsync(user); } + [Theory] + [BitAutoData] + public async Task PostKdf_UserNotFound_ShouldFail(PasswordRequestModel model) + { + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(Task.FromResult(null)); + + // Act + await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + } + [Theory] [BitAutoData] public async Task PostKdf_WithNullAuthenticationData_ShouldFail( @@ -623,7 +637,9 @@ public class AccountsControllerTests : IDisposable model.AuthenticationData = null; // Act - await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + var exception = await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + + Assert.Contains("AuthenticationData and UnlockData must be provided.", exception.Message); } [Theory] @@ -635,7 +651,41 @@ public class AccountsControllerTests : IDisposable model.UnlockData = null; // Act - await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + var exception = await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + + Assert.Contains("AuthenticationData and UnlockData must be provided.", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task PostKdf_ChangeKdfFailed_ShouldFail( + User user, PasswordRequestModel model) + { + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(Task.FromResult(user)); + _changeKdfCommand.ChangeKdfAsync(Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Failed(new IdentityError { Description = "Change KDF failed" }))); + + // Act + var exception = await Assert.ThrowsAsync(() => _sut.PostKdf(model)); + + Assert.NotNull(exception.ModelState); + Assert.Contains("Change KDF failed", + exception.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage)); + } + + [Theory] + [BitAutoData] + public async Task PostKdf_ChangeKdfSuccess_NoError( + User user, PasswordRequestModel model) + { + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(Task.FromResult(user)); + _changeKdfCommand.ChangeKdfAsync(Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Success)); + + // Act + await _sut.PostKdf(model); } // Below are helper functions that currently belong to this diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index 51866320ee..d79bfde893 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -1,5 +1,4 @@ using Bit.Api.Billing.Controllers; -using Bit.Api.Billing.Models.Responses; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Models; using Bit.Core.Billing.Organizations.Models; @@ -53,19 +52,16 @@ public class OrganizationBillingControllerTests { sutProvider.GetDependency().OrganizationUser(organizationId).Returns(true); sutProvider.GetDependency().GetMetadata(organizationId) - .Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null, 0)); + .Returns(new OrganizationMetadata(true, 10)); var result = await sutProvider.Sut.GetMetadataAsync(organizationId); - Assert.IsType>(result); + Assert.IsType>(result); - var response = ((Ok)result).Value; + var response = ((Ok)result).Value; - Assert.True(response.IsEligibleForSelfHost); - Assert.True(response.IsManaged); Assert.True(response.IsOnSecretsManagerStandalone); - Assert.True(response.IsSubscriptionUnpaid); - Assert.True(response.HasSubscription); + Assert.Equal(10, response.OrganizationOccupiedSeats); } [Theory, BitAutoData] diff --git a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs index 8c1dd60fb9..75bd13eae8 100644 --- a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs @@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Pricing; using Bit.Core.Billing.Providers.Entities; using Bit.Core.Billing.Providers.Repositories; @@ -270,7 +271,6 @@ public class ProviderBillingControllerTests var subscription = new Subscription { CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, - CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth), Customer = new Customer { Address = new Address @@ -291,20 +291,23 @@ public class ProviderBillingControllerTests Data = [ new SubscriptionItem { + CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth), Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise } }, new SubscriptionItem { + CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth), Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } } ] }, - Status = "unpaid", + Status = "unpaid" }; stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, Arg.Is( options => options.Expand.Contains("customer.tax_ids") && + options.Expand.Contains("discounts") && options.Expand.Contains("test_clock"))).Returns(subscription); var daysInLastMonth = DateTime.DaysInMonth(oneMonthAgo.Year, oneMonthAgo.Month); @@ -365,7 +368,7 @@ public class ProviderBillingControllerTests var response = ((Ok)result).Value; Assert.Equal(subscription.Status, response.Status); - Assert.Equal(subscription.CurrentPeriodEnd, response.CurrentPeriodEndDate); + Assert.Equal(subscription.GetCurrentPeriodEnd(), response.CurrentPeriodEndDate); Assert.Equal(subscription.Customer!.Discount!.Coupon!.PercentOff, response.DiscountPercentage); Assert.Equal(subscription.CollectionMethod, response.CollectionMethod); @@ -405,6 +408,118 @@ public class ProviderBillingControllerTests Assert.Equal(14, response.Suspension.GracePeriod); } + [Theory, BitAutoData] + public async Task GetSubscriptionAsync_SubscriptionLevelDiscount_Ok( + Provider provider, + SutProvider sutProvider) + { + ConfigureStableProviderServiceUserInputs(provider, sutProvider); + + var stripeAdapter = sutProvider.GetDependency(); + + var now = DateTime.UtcNow; + var oneMonthAgo = now.AddMonths(-1); + + var daysInThisMonth = DateTime.DaysInMonth(now.Year, now.Month); + + var subscription = new Subscription + { + CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, + Customer = new Customer + { + Address = new Address + { + Country = "US", + PostalCode = "12345", + Line1 = "123 Example St.", + Line2 = "Unit 1", + City = "Example Town", + State = "NY" + }, + Balance = -100000, + Discount = null, // No customer-level discount + TaxIds = new StripeList { Data = [new TaxId { Value = "123456789" }] } + }, + Discounts = + [ + new Discount { Coupon = new Coupon { PercentOff = 15 } } // Subscription-level discount + ], + Items = new StripeList + { + Data = [ + new SubscriptionItem + { + CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth), + Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Enterprise } + }, + new SubscriptionItem + { + CurrentPeriodEnd = new DateTime(now.Year, now.Month, daysInThisMonth), + Price = new Price { Id = ProviderPriceAdapter.MSP.Active.Teams } + } + ] + }, + Status = "active" + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, Arg.Is( + options => + options.Expand.Contains("customer.tax_ids") && + options.Expand.Contains("discounts") && + options.Expand.Contains("test_clock"))).Returns(subscription); + + stripeAdapter.InvoiceSearchAsync(Arg.Is( + options => options.Query == $"subscription:'{subscription.Id}' status:'open'")) + .Returns([]); + + var providerPlans = new List + { + new () + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.TeamsMonthly, + SeatMinimum = 50, + PurchasedSeats = 10, + AllocatedSeats = 60 + }, + new () + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.EnterpriseMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 90 + } + }; + + sutProvider.GetDependency().GetByProviderId(provider.Id).Returns(providerPlans); + + foreach (var providerPlan in providerPlans) + { + var plan = StaticStore.GetPlan(providerPlan.PlanType); + sutProvider.GetDependency().GetPlanOrThrow(providerPlan.PlanType).Returns(plan); + var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType); + sutProvider.GetDependency().PriceGetAsync(priceId) + .Returns(new Price + { + UnitAmountDecimal = plan.PasswordManager.ProviderPortalSeatPrice * 100 + }); + } + + var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id); + + Assert.IsType>(result); + + var response = ((Ok)result).Value; + + Assert.Equal(subscription.Status, response.Status); + Assert.Equal(subscription.GetCurrentPeriodEnd(), response.CurrentPeriodEndDate); + Assert.Equal(15, response.DiscountPercentage); // Verify subscription-level discount is used + Assert.Equal(subscription.CollectionMethod, response.CollectionMethod); + } + #endregion #region UpdateTaxInformationAsync diff --git a/test/Api.Test/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs b/test/Api.Test/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs index 05b1aa5a4d..2e41dd79a0 100644 --- a/test/Api.Test/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs +++ b/test/Api.Test/KeyManagement/Controllers/AccountsKeyManagementControllerTests.cs @@ -110,6 +110,7 @@ public class AccountsKeyManagementControllerTests public async Task RotateUserAccountKeysSuccess(SutProvider sutProvider, RotateUserAccountKeysAndDataRequestModel data, User user) { + data.AccountKeys.SignatureKeyPair = null; sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); sutProvider.GetDependency().RotateUserAccountKeysAsync(Arg.Any(), Arg.Any()) .Returns(IdentityResult.Success); @@ -142,8 +143,60 @@ public class AccountsKeyManagementControllerTests && d.MasterPasswordUnlockData.MasterKeyAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash && d.MasterPasswordUnlockData.MasterKeyEncryptedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey - && d.AccountPublicKey == data.AccountKeys.AccountPublicKey - && d.UserKeyEncryptedAccountPrivateKey == data.AccountKeys.UserKeyEncryptedAccountPrivateKey + && d.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey + && d.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey + )); + } + + [Theory] + [BitAutoData] + public async Task RotateUserAccountKeys_UserCryptoV2_Success_Async(SutProvider sutProvider, + RotateUserAccountKeysAndDataRequestModel data, User user) + { + data.AccountKeys.SignatureKeyPair = new SignatureKeyPairRequestModel + { + SignatureAlgorithm = "ed25519", + WrappedSigningKey = "wrappedSigningKey", + VerifyingKey = "verifyingKey" + }; + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); + sutProvider.GetDependency().RotateUserAccountKeysAsync(Arg.Any(), Arg.Any()) + .Returns(IdentityResult.Success); + await sutProvider.Sut.RotateUserAccountKeysAsync(data); + + await sutProvider.GetDependency, IEnumerable>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountUnlockData.EmergencyAccessUnlockData)); + await sutProvider.GetDependency, IReadOnlyList>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountUnlockData.OrganizationAccountRecoveryUnlockData)); + await sutProvider.GetDependency, IEnumerable>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountUnlockData.PasskeyUnlockData)); + + await sutProvider.GetDependency, IEnumerable>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountData.Ciphers)); + await sutProvider.GetDependency, IEnumerable>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountData.Folders)); + await sutProvider.GetDependency, IReadOnlyList>>().Received(1) + .ValidateAsync(Arg.Any(), Arg.Is(data.AccountData.Sends)); + + await sutProvider.GetDependency().Received(1) + .RotateUserAccountKeysAsync(Arg.Is(user), Arg.Is(d => + d.OldMasterKeyAuthenticationHash == data.OldMasterKeyAuthenticationHash + + && d.MasterPasswordUnlockData.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType + && d.MasterPasswordUnlockData.KdfIterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations + && d.MasterPasswordUnlockData.KdfMemory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory + && d.MasterPasswordUnlockData.KdfParallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism + && d.MasterPasswordUnlockData.Email == data.AccountUnlockData.MasterPasswordUnlockData.Email + + && d.MasterPasswordUnlockData.MasterKeyAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash + && d.MasterPasswordUnlockData.MasterKeyEncryptedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey + + && d.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey + && d.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey + && d.AccountKeys!.PublicKeyEncryptionKeyPairData.SignedPublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.SignedPublicKey + && d.AccountKeys!.SignatureKeyPairData!.SignatureAlgorithm == Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519 + && d.AccountKeys!.SignatureKeyPairData.WrappedSigningKey == data.AccountKeys.SignatureKeyPair!.WrappedSigningKey + && d.AccountKeys!.SignatureKeyPairData.VerifyingKey == data.AccountKeys.SignatureKeyPair!.VerifyingKey )); } @@ -153,6 +206,7 @@ public class AccountsKeyManagementControllerTests public async Task RotateUserKeyNoUser_Throws(SutProvider sutProvider, RotateUserAccountKeysAndDataRequestModel data) { + data.AccountKeys.SignatureKeyPair = null; User? user = null; sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); sutProvider.GetDependency().RotateUserAccountKeysAsync(Arg.Any(), Arg.Any()) @@ -165,6 +219,7 @@ public class AccountsKeyManagementControllerTests public async Task RotateUserKeyWrongData_Throws(SutProvider sutProvider, RotateUserAccountKeysAndDataRequestModel data, User user, IdentityErrorDescriber _identityErrorDescriber) { + data.AccountKeys.SignatureKeyPair = null; sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); sutProvider.GetDependency().RotateUserAccountKeysAsync(Arg.Any(), Arg.Any()) .Returns(IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch())); diff --git a/test/Api.Test/KeyManagement/Controllers/UsersControllerTests.cs b/test/Api.Test/KeyManagement/Controllers/UsersControllerTests.cs new file mode 100644 index 0000000000..6e3094234b --- /dev/null +++ b/test/Api.Test/KeyManagement/Controllers/UsersControllerTests.cs @@ -0,0 +1,112 @@ +#nullable enable +using Bit.Api.KeyManagement.Controllers; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Enums; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries.Interfaces; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Api.Test.KeyManagement.Controllers; + +[ControllerCustomize(typeof(UsersController))] +[SutProviderCustomize] +[JsonDocumentCustomize] +public class UsersControllerTests +{ + [Theory] + [BitAutoData] + public async Task GetPublicKey_NotFound_ThrowsNotFoundException( + SutProvider sutProvider) + { + sutProvider.GetDependency().GetPublicKeyAsync(Arg.Any()).ReturnsNull(); + await Assert.ThrowsAsync(() => sutProvider.Sut.GetPublicKeyAsync(new Guid())); + } + + [Theory] + [BitAutoData] + public async Task GetPublicKey_ReturnsUserKeyResponseModel( + SutProvider sutProvider, + Guid userId) + { + var publicKey = "publicKey"; + sutProvider.GetDependency().GetPublicKeyAsync(userId).Returns(publicKey); + + var result = await sutProvider.Sut.GetPublicKeyAsync(userId); + Assert.NotNull(result); + Assert.Equal(userId, result.UserId); + Assert.Equal(publicKey, result.PublicKey); + } + + [Theory] + [BitAutoData] + public async Task GetAccountKeys_UserNotFound_ThrowsNotFoundException( + SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(Arg.Any()).ReturnsNull(); + await Assert.ThrowsAsync(() => sutProvider.Sut.GetAccountKeysAsync(new Guid())); + } + + [Theory] + [BitAutoData] + public async Task GetAccountKeys_ReturnsPublicUserKeysResponseModel( + SutProvider sutProvider, + Guid userId) + { + var user = new User + { + Id = userId, + PublicKey = "publicKey", + SignedPublicKey = "signedPublicKey", + }; + + sutProvider.GetDependency().GetByIdAsync(userId).Returns(user); + sutProvider.GetDependency() + .Run(user) + .Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("wrappedPrivateKey", "publicKey", "signedPublicKey"), + SignatureKeyPairData = new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "wrappedSigningKey", "verifyingKey"), + }); + + var result = await sutProvider.Sut.GetAccountKeysAsync(userId); + Assert.NotNull(result); + Assert.Equal("publicKey", result.PublicKey); + Assert.Equal("signedPublicKey", result.SignedPublicKey); + Assert.Equal("verifyingKey", result.VerifyingKey); + } + + [Theory] + [BitAutoData] + public async Task GetAccountKeys_ReturnsPublicUserKeysResponseModel_WithNullVerifyingKey( + SutProvider sutProvider, + Guid userId) + { + var user = new User + { + Id = userId, + PublicKey = "publicKey", + SignedPublicKey = null, + }; + + sutProvider.GetDependency().GetByIdAsync(userId).Returns(user); + sutProvider.GetDependency() + .Run(user) + .Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("wrappedPrivateKey", "publicKey", null), + SignatureKeyPairData = null, + }); + + var result = await sutProvider.Sut.GetAccountKeysAsync(userId); + Assert.NotNull(result); + Assert.Equal("publicKey", result.PublicKey); + Assert.Null(result.SignedPublicKey); + Assert.Null(result.VerifyingKey); + } +} diff --git a/test/Api.Test/KeyManagement/Models/Request/SignatureKeyPairRequestModel.cs b/test/Api.Test/KeyManagement/Models/Request/SignatureKeyPairRequestModel.cs new file mode 100644 index 0000000000..704371eebd --- /dev/null +++ b/test/Api.Test/KeyManagement/Models/Request/SignatureKeyPairRequestModel.cs @@ -0,0 +1,22 @@ +#nullable enable + +using Bit.Api.KeyManagement.Models.Requests; +using Xunit; + +namespace Bit.Api.Test.KeyManagement.Models.Request; + +public class SignatureKeyPairRequestModelTests +{ + [Fact] + public void ToSignatureKeyPairData_WrongAlgorithm_Rejects() + { + var model = new SignatureKeyPairRequestModel + { + SignatureAlgorithm = "abc", + WrappedSigningKey = "wrappedKey", + VerifyingKey = "verifyingKey" + }; + + Assert.Throws(() => model.ToSignatureKeyPairData()); + } +} diff --git a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs index 416b92f841..9f54cdbea5 100644 --- a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs @@ -1790,6 +1790,118 @@ public class CiphersControllerTests ); } + [Theory, BitAutoData] + public async Task PutShareMany_ArchivedCipher_ThrowsBadRequestException( + Guid organizationId, + Guid userId, + CipherWithIdRequestModel request, + SutProvider sutProvider) + { + request.EncryptedFor = userId; + request.OrganizationId = organizationId.ToString(); + request.ArchivedDate = DateTime.UtcNow; + var model = new CipherBulkShareRequestModel + { + Ciphers = [request], + CollectionIds = [Guid.NewGuid().ToString()] + }; + + sutProvider.GetDependency() + .OrganizationUser(organizationId) + .Returns(Task.FromResult(true)); + sutProvider.GetDependency() + .GetProperUserId(default) + .ReturnsForAnyArgs(userId); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.PutShareMany(model) + ); + + Assert.Equal("Cannot move archived items to an organization.", exception.Message); + } + + [Theory, BitAutoData] + public async Task PutShareMany_ExistingCipherArchived_ThrowsBadRequestException( + Guid organizationId, + Guid userId, + CipherWithIdRequestModel request, + SutProvider sutProvider) + { + // Request model does not have ArchivedDate (only the existing cipher does) + request.EncryptedFor = userId; + request.OrganizationId = organizationId.ToString(); + request.ArchivedDate = null; + + var model = new CipherBulkShareRequestModel + { + Ciphers = [request], + CollectionIds = [Guid.NewGuid().ToString()] + }; + + // The existing cipher from the repository IS archived + var existingCipher = new CipherDetails + { + Id = request.Id!.Value, + UserId = userId, + Type = CipherType.Login, + Data = JsonSerializer.Serialize(new CipherLoginData()), + ArchivedDate = DateTime.UtcNow + }; + + sutProvider.GetDependency() + .OrganizationUser(organizationId) + .Returns(Task.FromResult(true)); + sutProvider.GetDependency() + .GetProperUserId(default) + .ReturnsForAnyArgs(userId); + sutProvider.GetDependency() + .GetManyByUserIdAsync(userId, withOrganizations: false) + .Returns(Task.FromResult((ICollection)[existingCipher])); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.PutShareMany(model) + ); + + Assert.Equal("Cannot move archived items to an organization.", exception.Message); + } + + [Theory, BitAutoData] + public async Task PutShare_ArchivedCipher_ThrowsBadRequestException( + Guid cipherId, + Guid organizationId, + User user, + CipherShareRequestModel model, + SutProvider sutProvider) + { + model.Cipher.OrganizationId = organizationId.ToString(); + model.Cipher.EncryptedFor = user.Id; + + var cipher = new Cipher + { + Id = cipherId, + UserId = user.Id, + ArchivedDate = DateTime.UtcNow.AddDays(-1), + Type = CipherType.Login, + Data = JsonSerializer.Serialize(new CipherLoginData()) + }; + + sutProvider.GetDependency() + .GetUserByPrincipalAsync(Arg.Any()) + .Returns(user); + sutProvider.GetDependency() + .GetByIdAsync(cipherId) + .Returns(cipher); + sutProvider.GetDependency() + .OrganizationUser(organizationId) + .Returns(Task.FromResult(true)); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.PutShare(cipherId, model) + ); + + Assert.Equal("Cannot move an archived item to an organization.", exception.Message); + } + [Theory, BitAutoData] public async Task PostPurge_WhenUserNotFound_ThrowsUnauthorizedAccessException( SecretVerificationRequestModel model, diff --git a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs index 54db1e4053..aaee8e0c2c 100644 --- a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs @@ -12,6 +12,8 @@ using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; @@ -74,6 +76,7 @@ public class SyncControllerTests var policyRepository = sutProvider.GetDependency(); var collectionRepository = sutProvider.GetDependency(); var collectionCipherRepository = sutProvider.GetDependency(); + var userAccountKeysQuery = sutProvider.GetDependency(); // Adjust random data to match required formats / test intentions user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains); @@ -98,6 +101,11 @@ public class SyncControllerTests // Setup returns userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); organizationUserRepository .GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails); @@ -127,7 +135,6 @@ public class SyncControllerTests // Execute GET var result = await sutProvider.Sut.Get(); - // Asserts // Assert that methods are called var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled); @@ -166,6 +173,7 @@ public class SyncControllerTests var policyRepository = sutProvider.GetDependency(); var collectionRepository = sutProvider.GetDependency(); var collectionCipherRepository = sutProvider.GetDependency(); + var userAccountKeysQuery = sutProvider.GetDependency(); // Adjust random data to match required formats / test intentions user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains); @@ -189,6 +197,11 @@ public class SyncControllerTests // Setup returns userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); organizationUserRepository .GetManyDetailsByUserAsync(user.Id, OrganizationUserStatusType.Confirmed).Returns(organizationUserDetails); @@ -256,6 +269,7 @@ public class SyncControllerTests var policyRepository = sutProvider.GetDependency(); var collectionRepository = sutProvider.GetDependency(); var collectionCipherRepository = sutProvider.GetDependency(); + var userAccountKeysQuery = sutProvider.GetDependency(); // Adjust random data to match required formats / test intentions user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains); @@ -290,6 +304,12 @@ public class SyncControllerTests twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false); userService.HasPremiumFromOrganization(user).Returns(false); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); + // Execute GET var result = await sutProvider.Sut.Get(); @@ -327,6 +347,13 @@ public class SyncControllerTests user.MasterPassword = null; + var userAccountKeysQuery = sutProvider.GetDependency(); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); + var userService = sutProvider.GetDependency(); userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); @@ -352,6 +379,13 @@ public class SyncControllerTests user.KdfMemory = kdfMemory; user.KdfParallelism = kdfParallelism; + var userAccountKeysQuery = sutProvider.GetDependency(); + userAccountKeysQuery.Run(user).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = user.GetPublicKeyEncryptionKeyPair(), + SignatureKeyPairData = null, + }); + var userService = sutProvider.GetDependency(); userService.GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); diff --git a/test/Billing.Test/Billing.Test.csproj b/test/Billing.Test/Billing.Test.csproj index b4ea2938f6..4d7f887c90 100644 --- a/test/Billing.Test/Billing.Test.csproj +++ b/test/Billing.Test/Billing.Test.csproj @@ -27,24 +27,6 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -73,9 +55,6 @@ PreserveNewest - - PreserveNewest - diff --git a/test/Billing.Test/Resources/Events/charge.succeeded.json b/test/Billing.Test/Resources/Events/charge.succeeded.json deleted file mode 100644 index 3cf919f123..0000000000 --- a/test/Billing.Test/Resources/Events/charge.succeeded.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "id": "evt_3NvKgBIGBnsLynRr0pJJqudS", - "object": "event", - "api_version": "2024-06-20", - "created": 1695909300, - "data": { - "object": { - "id": "ch_3NvKgBIGBnsLynRr0ZyvP9AN", - "object": "charge", - "amount": 7200, - "amount_captured": 7200, - "amount_refunded": 0, - "application": null, - "application_fee": null, - "application_fee_amount": null, - "balance_transaction": "txn_3NvKgBIGBnsLynRr0KbYEz76", - "billing_details": { - "address": { - "city": null, - "country": null, - "line1": null, - "line2": null, - "postal_code": null, - "state": null - }, - "email": null, - "name": null, - "phone": null - }, - "calculated_statement_descriptor": "BITWARDEN", - "captured": true, - "created": 1695909299, - "currency": "usd", - "customer": "cus_OimAwOzQmThNXx", - "description": "Subscription update", - "destination": null, - "dispute": null, - "disputed": false, - "failure_balance_transaction": null, - "failure_code": null, - "failure_message": null, - "fraud_details": { - }, - "invoice": "in_1NvKgBIGBnsLynRrmRFHAcoV", - "livemode": false, - "metadata": { - }, - "on_behalf_of": null, - "order": null, - "outcome": { - "network_status": "approved_by_network", - "reason": null, - "risk_level": "normal", - "risk_score": 37, - "seller_message": "Payment complete.", - "type": "authorized" - }, - "paid": true, - "payment_intent": "pi_3NvKgBIGBnsLynRr09Ny3Heu", - "payment_method": "pm_1NvKbpIGBnsLynRrcOwez4A1", - "payment_method_details": { - "card": { - "amount_authorized": 7200, - "brand": "visa", - "checks": { - "address_line1_check": null, - "address_postal_code_check": null, - "cvc_check": "pass" - }, - "country": "US", - "exp_month": 6, - "exp_year": 2033, - "extended_authorization": { - "status": "disabled" - }, - "fingerprint": "0VgUBpvqcUUnuSmK", - "funding": "credit", - "incremental_authorization": { - "status": "unavailable" - }, - "installments": null, - "last4": "4242", - "mandate": null, - "multicapture": { - "status": "unavailable" - }, - "network": "visa", - "network_token": { - "used": false - }, - "overcapture": { - "maximum_amount_capturable": 7200, - "status": "unavailable" - }, - "three_d_secure": null, - "wallet": null - }, - "type": "card" - }, - "receipt_email": "cturnbull@bitwarden.com", - "receipt_number": null, - "receipt_url": "https://pay.stripe.com/receipts/invoices/CAcaFwoVYWNjdF8xOXNtSVhJR0Juc0x5blJyKLSL1qgGMgYTnk_JOUA6LBY_SDEZNtuae1guQ6Dlcuev1TUHwn712t-UNnZdIc383zS15bXv_1dby8e4?s=ap", - "refunded": false, - "refunds": { - "object": "list", - "data": [ - ], - "has_more": false, - "total_count": 0, - "url": "/v1/charges/ch_3NvKgBIGBnsLynRr0ZyvP9AN/refunds" - }, - "review": null, - "shipping": null, - "source": null, - "source_transfer": null, - "statement_descriptor": null, - "statement_descriptor_suffix": null, - "status": "succeeded", - "transfer_data": null, - "transfer_group": null - } - }, - "livemode": false, - "pending_webhooks": 9, - "request": { - "id": "req_rig8N5Ca8EXYRy", - "idempotency_key": "db75068d-5d90-4c65-a410-4e2ed8347509" - }, - "type": "charge.succeeded" -} diff --git a/test/Billing.Test/Resources/Events/customer.subscription.updated.json b/test/Billing.Test/Resources/Events/customer.subscription.updated.json deleted file mode 100644 index 62a8590fa8..0000000000 --- a/test/Billing.Test/Resources/Events/customer.subscription.updated.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "id": "evt_1NvLMDIGBnsLynRr6oBxebrE", - "object": "event", - "api_version": "2024-06-20", - "created": 1695911902, - "data": { - "object": { - "id": "sub_1NvKoKIGBnsLynRrcLIAUWGf", - "object": "subscription", - "application": null, - "application_fee_percent": null, - "automatic_tax": { - "enabled": false - }, - "billing_cycle_anchor": 1695911900, - "billing_thresholds": null, - "cancel_at": null, - "cancel_at_period_end": false, - "canceled_at": null, - "cancellation_details": { - "comment": null, - "feedback": null, - "reason": null - }, - "collection_method": "charge_automatically", - "created": 1695909804, - "currency": "usd", - "current_period_end": 1727534300, - "current_period_start": 1695911900, - "customer": "cus_OimNNCC3RiI2HQ", - "days_until_due": null, - "default_payment_method": null, - "default_source": null, - "default_tax_rates": [ - ], - "description": null, - "discount": null, - "ended_at": null, - "items": { - "object": "list", - "data": [ - { - "id": "si_OimNgVtrESpqus", - "object": "subscription_item", - "billing_thresholds": null, - "created": 1695909805, - "metadata": { - }, - "plan": { - "id": "enterprise-org-seat-annually", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 3600, - "amount_decimal": "3600", - "billing_scheme": "per_unit", - "created": 1494268677, - "currency": "usd", - "interval": "year", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "nickname": "2019 Enterprise Seat (Annually)", - "product": "prod_BUtogGemxnTi9z", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "price": { - "id": "enterprise-org-seat-annually", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1494268677, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "2019 Enterprise Seat (Annually)", - "product": "prod_BUtogGemxnTi9z", - "recurring": { - "aggregate_usage": null, - "interval": "year", - "interval_count": 1, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "unspecified", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 3600, - "unit_amount_decimal": "3600" - }, - "quantity": 1, - "subscription": "sub_1NvKoKIGBnsLynRrcLIAUWGf", - "tax_rates": [ - ] - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/subscription_items?subscription=sub_1NvKoKIGBnsLynRrcLIAUWGf" - }, - "latest_invoice": "in_1NvLM9IGBnsLynRrOysII07d", - "livemode": false, - "metadata": { - "organizationId": "84a569ea-4643-474a-83a9-b08b00e7a20d" - }, - "next_pending_invoice_item_invoice": null, - "on_behalf_of": null, - "pause_collection": null, - "payment_settings": { - "payment_method_options": null, - "payment_method_types": null, - "save_default_payment_method": "off" - }, - "pending_invoice_item_interval": null, - "pending_setup_intent": null, - "pending_update": null, - "plan": { - "id": "enterprise-org-seat-annually", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 3600, - "amount_decimal": "3600", - "billing_scheme": "per_unit", - "created": 1494268677, - "currency": "usd", - "interval": "year", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "nickname": "2019 Enterprise Seat (Annually)", - "product": "prod_BUtogGemxnTi9z", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "quantity": 1, - "schedule": null, - "start_date": 1695909804, - "status": "active", - "test_clock": null, - "transfer_data": null, - "trial_end": 1695911899, - "trial_settings": { - "end_behavior": { - "missing_payment_method": "create_invoice" - } - }, - "trial_start": 1695909804 - }, - "previous_attributes": { - "billing_cycle_anchor": 1696514604, - "current_period_end": 1696514604, - "current_period_start": 1695909804, - "latest_invoice": "in_1NvKoKIGBnsLynRrSNRC6oYI", - "status": "trialing", - "trial_end": 1696514604 - } - }, - "livemode": false, - "pending_webhooks": 8, - "request": { - "id": "req_DMZPUU3BI66zAx", - "idempotency_key": "3fd8b4a5-6a20-46ab-9f45-b37b02a8017f" - }, - "type": "customer.subscription.updated" -} diff --git a/test/Billing.Test/Resources/Events/customer.updated.json b/test/Billing.Test/Resources/Events/customer.updated.json deleted file mode 100644 index 9aa0928515..0000000000 --- a/test/Billing.Test/Resources/Events/customer.updated.json +++ /dev/null @@ -1,311 +0,0 @@ -{ - "id": "evt_1NvKjSIGBnsLynRrS3MTK4DZ", - "object": "event", - "account": "acct_19smIXIGBnsLynRr", - "api_version": "2024-06-20", - "created": 1695909502, - "data": { - "object": { - "id": "cus_Of54kUr3gV88lM", - "object": "customer", - "address": { - "city": null, - "country": "US", - "line1": "", - "line2": null, - "postal_code": "33701", - "state": null - }, - "balance": 0, - "created": 1695056798, - "currency": "usd", - "default_source": "src_1NtAfeIGBnsLynRrYDrceax7", - "delinquent": false, - "description": "Premium User", - "discount": null, - "email": "premium@bitwarden.com", - "invoice_prefix": "C506E8CE", - "invoice_settings": { - "custom_fields": [ - { - "name": "Subscriber", - "value": "Premium User" - } - ], - "default_payment_method": "pm_1Nrku9IGBnsLynRrcsQ3hy6C", - "footer": null, - "rendering_options": null - }, - "livemode": false, - "metadata": { - "region": "US" - }, - "name": null, - "next_invoice_sequence": 2, - "phone": null, - "preferred_locales": [ - ], - "shipping": null, - "tax_exempt": "none", - "test_clock": null, - "account_balance": 0, - "cards": { - "object": "list", - "data": [ - ], - "has_more": false, - "total_count": 0, - "url": "/v1/customers/cus_Of54kUr3gV88lM/cards" - }, - "default_card": null, - "default_currency": "usd", - "sources": { - "object": "list", - "data": [ - { - "id": "src_1NtAfeIGBnsLynRrYDrceax7", - "object": "source", - "ach_credit_transfer": { - "account_number": "test_b2d1c6415f6f", - "routing_number": "110000000", - "fingerprint": "ePO4hBQanSft3gvU", - "swift_code": "TSTEZ122", - "bank_name": "TEST BANK", - "refund_routing_number": null, - "refund_account_holder_type": null, - "refund_account_holder_name": null - }, - "amount": null, - "client_secret": "src_client_secret_bUAP2uDRw6Pwj0xYk32LmJ3K", - "created": 1695394170, - "currency": "usd", - "customer": "cus_Of54kUr3gV88lM", - "flow": "receiver", - "livemode": false, - "metadata": { - }, - "owner": { - "address": null, - "email": "amount_0@stripe.com", - "name": null, - "phone": null, - "verified_address": null, - "verified_email": null, - "verified_name": null, - "verified_phone": null - }, - "receiver": { - "address": "110000000-test_b2d1c6415f6f", - "amount_charged": 0, - "amount_received": 0, - "amount_returned": 0, - "refund_attributes_method": "email", - "refund_attributes_status": "missing" - }, - "statement_descriptor": null, - "status": "pending", - "type": "ach_credit_transfer", - "usage": "reusable" - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/customers/cus_Of54kUr3gV88lM/sources" - }, - "subscriptions": { - "object": "list", - "data": [ - { - "id": "sub_1NrkuBIGBnsLynRrzjFGIjEw", - "object": "subscription", - "application": null, - "application_fee_percent": null, - "automatic_tax": { - "enabled": false - }, - "billing": "charge_automatically", - "billing_cycle_anchor": 1695056799, - "billing_thresholds": null, - "cancel_at": null, - "cancel_at_period_end": false, - "canceled_at": null, - "cancellation_details": { - "comment": null, - "feedback": null, - "reason": null - }, - "collection_method": "charge_automatically", - "created": 1695056799, - "currency": "usd", - "current_period_end": 1726679199, - "current_period_start": 1695056799, - "customer": "cus_Of54kUr3gV88lM", - "days_until_due": null, - "default_payment_method": null, - "default_source": null, - "default_tax_rates": [ - ], - "description": null, - "discount": null, - "ended_at": null, - "invoice_customer_balance_settings": { - "consume_applied_balance_on_void": true - }, - "items": { - "object": "list", - "data": [ - { - "id": "si_Of54i3aK9I5Wro", - "object": "subscription_item", - "billing_thresholds": null, - "created": 1695056800, - "metadata": { - }, - "plan": { - "id": "premium-annually", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 1000, - "amount_decimal": "1000", - "billing_scheme": "per_unit", - "created": 1499289328, - "currency": "usd", - "interval": "year", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "name": "Premium (Annually)", - "nickname": "Premium (Annually)", - "product": "prod_BUqgYr48VzDuCg", - "statement_description": null, - "statement_descriptor": null, - "tiers": null, - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "price": { - "id": "premium-annually", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1499289328, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "Premium (Annually)", - "product": "prod_BUqgYr48VzDuCg", - "recurring": { - "aggregate_usage": null, - "interval": "year", - "interval_count": 1, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "unspecified", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 1000, - "unit_amount_decimal": "1000" - }, - "quantity": 1, - "subscription": "sub_1NrkuBIGBnsLynRrzjFGIjEw", - "tax_rates": [ - ] - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/subscription_items?subscription=sub_1NrkuBIGBnsLynRrzjFGIjEw" - }, - "latest_invoice": "in_1NrkuBIGBnsLynRr40gyJTVU", - "livemode": false, - "metadata": { - "userId": "91f40b6d-ac3b-4348-804b-b0810119ac6a" - }, - "next_pending_invoice_item_invoice": null, - "on_behalf_of": null, - "pause_collection": null, - "payment_settings": { - "payment_method_options": null, - "payment_method_types": null, - "save_default_payment_method": "off" - }, - "pending_invoice_item_interval": null, - "pending_setup_intent": null, - "pending_update": null, - "plan": { - "id": "premium-annually", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 1000, - "amount_decimal": "1000", - "billing_scheme": "per_unit", - "created": 1499289328, - "currency": "usd", - "interval": "year", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "name": "Premium (Annually)", - "nickname": "Premium (Annually)", - "product": "prod_BUqgYr48VzDuCg", - "statement_description": null, - "statement_descriptor": null, - "tiers": null, - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "quantity": 1, - "schedule": null, - "start": 1695056799, - "start_date": 1695056799, - "status": "active", - "tax_percent": null, - "test_clock": null, - "transfer_data": null, - "trial_end": null, - "trial_settings": { - "end_behavior": { - "missing_payment_method": "create_invoice" - } - }, - "trial_start": null - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/customers/cus_Of54kUr3gV88lM/subscriptions" - }, - "tax_ids": { - "object": "list", - "data": [ - ], - "has_more": false, - "total_count": 0, - "url": "/v1/customers/cus_Of54kUr3gV88lM/tax_ids" - }, - "tax_info": null, - "tax_info_verification": null - }, - "previous_attributes": { - "email": "premium-new@bitwarden.com" - } - }, - "livemode": false, - "pending_webhooks": 5, - "request": "req_2RtGdXCfiicFLx", - "type": "customer.updated", - "user_id": "acct_19smIXIGBnsLynRr" -} diff --git a/test/Billing.Test/Resources/Events/invoice.created.json b/test/Billing.Test/Resources/Events/invoice.created.json deleted file mode 100644 index bf53372b51..0000000000 --- a/test/Billing.Test/Resources/Events/invoice.created.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "id": "evt_1NvKzfIGBnsLynRr0SkwrlkE", - "object": "event", - "api_version": "2024-06-20", - "created": 1695910506, - "data": { - "object": { - "id": "in_1NvKzdIGBnsLynRr8fE8cpbg", - "object": "invoice", - "account_country": "US", - "account_name": "Bitwarden Inc.", - "account_tax_ids": null, - "amount_due": 0, - "amount_paid": 0, - "amount_remaining": 0, - "amount_shipping": 0, - "application": null, - "application_fee_amount": null, - "attempt_count": 0, - "attempted": true, - "auto_advance": false, - "automatic_tax": { - "enabled": false, - "status": null - }, - "billing_reason": "subscription_create", - "charge": null, - "collection_method": "charge_automatically", - "created": 1695910505, - "currency": "usd", - "custom_fields": [ - { - "name": "Organization", - "value": "teams 2023 monthly - 2" - } - ], - "customer": "cus_OimYrxnMTMMK1E", - "customer_address": { - "city": null, - "country": "US", - "line1": "", - "line2": null, - "postal_code": "12345", - "state": null - }, - "customer_email": "cturnbull@bitwarden.com", - "customer_name": null, - "customer_phone": null, - "customer_shipping": null, - "customer_tax_exempt": "none", - "customer_tax_ids": [ - ], - "default_payment_method": null, - "default_source": null, - "default_tax_rates": [ - ], - "description": null, - "discount": null, - "discounts": [ - ], - "due_date": null, - "effective_at": 1695910505, - "ending_balance": 0, - "footer": null, - "from_invoice": null, - "hosted_invoice_url": "https://invoice.stripe.com/i/acct_19smIXIGBnsLynRr/test_YWNjdF8xOXNtSVhJR0Juc0x5blJyLF9PaW1ZVlo4dFRtbkNQQVY5aHNpckQxN1QzRHBPcVBOLDg2NDUxMzA30200etYRHca2?s=ap", - "invoice_pdf": "https://pay.stripe.com/invoice/acct_19smIXIGBnsLynRr/test_YWNjdF8xOXNtSVhJR0Juc0x5blJyLF9PaW1ZVlo4dFRtbkNQQVY5aHNpckQxN1QzRHBPcVBOLDg2NDUxMzA30200etYRHca2/pdf?s=ap", - "last_finalization_error": null, - "latest_revision": null, - "lines": { - "object": "list", - "data": [ - { - "id": "il_1NvKzdIGBnsLynRr2pS4ZA8e", - "object": "line_item", - "amount": 0, - "amount_excluding_tax": 0, - "currency": "usd", - "description": "Trial period for Teams Organization Seat", - "discount_amounts": [ - ], - "discountable": true, - "discounts": [ - ], - "livemode": false, - "metadata": { - "organizationId": "3fbc84ce-102d-4919-b89b-b08b00ead71a" - }, - "period": { - "end": 1696515305, - "start": 1695910505 - }, - "plan": { - "id": "2020-teams-org-seat-monthly", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 400, - "amount_decimal": "400", - "billing_scheme": "per_unit", - "created": 1595263113, - "currency": "usd", - "interval": "month", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "nickname": "Teams Organization Seat (Monthly) 2023", - "product": "prod_HgOooYXDr2DDAA", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "price": { - "id": "2020-teams-org-seat-monthly", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1595263113, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "Teams Organization Seat (Monthly) 2023", - "product": "prod_HgOooYXDr2DDAA", - "recurring": { - "aggregate_usage": null, - "interval": "month", - "interval_count": 1, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "unspecified", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 400, - "unit_amount_decimal": "400" - }, - "proration": false, - "proration_details": { - "credited_items": null - }, - "quantity": 1, - "subscription": "sub_1NvKzdIGBnsLynRrKIHQamZc", - "subscription_item": "si_OimYNSbvuqdtTr", - "tax_amounts": [ - ], - "tax_rates": [ - ], - "type": "subscription", - "unit_amount_excluding_tax": "0" - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/invoices/in_1NvKzdIGBnsLynRr8fE8cpbg/lines" - }, - "livemode": false, - "metadata": { - }, - "next_payment_attempt": null, - "number": "3E96D078-0001", - "on_behalf_of": null, - "paid": true, - "paid_out_of_band": false, - "payment_intent": null, - "payment_settings": { - "default_mandate": null, - "payment_method_options": null, - "payment_method_types": null - }, - "period_end": 1695910505, - "period_start": 1695910505, - "post_payment_credit_notes_amount": 0, - "pre_payment_credit_notes_amount": 0, - "quote": null, - "receipt_number": null, - "rendering": null, - "rendering_options": null, - "shipping_cost": null, - "shipping_details": null, - "starting_balance": 0, - "statement_descriptor": null, - "status": "paid", - "status_transitions": { - "finalized_at": 1695910505, - "marked_uncollectible_at": null, - "paid_at": 1695910505, - "voided_at": null - }, - "subscription": "sub_1NvKzdIGBnsLynRrKIHQamZc", - "subscription_details": { - "metadata": { - "organizationId": "3fbc84ce-102d-4919-b89b-b08b00ead71a" - } - }, - "subtotal": 0, - "subtotal_excluding_tax": 0, - "tax": null, - "test_clock": null, - "total": 0, - "total_discount_amounts": [ - ], - "total_excluding_tax": 0, - "total_tax_amounts": [ - ], - "transfer_data": null, - "webhooks_delivered_at": null - } - }, - "livemode": false, - "pending_webhooks": 8, - "request": { - "id": "req_roIwONfgyfZdr4", - "idempotency_key": "dd2a171b-b9c7-4d2d-89d5-1ceae3c0595d" - }, - "type": "invoice.created" -} diff --git a/test/Billing.Test/Resources/Events/invoice.finalized.json b/test/Billing.Test/Resources/Events/invoice.finalized.json deleted file mode 100644 index 207fab497e..0000000000 --- a/test/Billing.Test/Resources/Events/invoice.finalized.json +++ /dev/null @@ -1,400 +0,0 @@ -{ - "id": "evt_1PQaABIGBnsLynRrhoJjGnyz", - "object": "event", - "account": "acct_19smIXIGBnsLynRr", - "api_version": "2024-06-20", - "created": 1718133319, - "data": { - "object": { - "id": "in_1PQa9fIGBnsLynRraYIqTdBs", - "object": "invoice", - "account_country": "US", - "account_name": "Bitwarden Inc.", - "account_tax_ids": null, - "amount_due": 84240, - "amount_paid": 0, - "amount_remaining": 84240, - "amount_shipping": 0, - "application": null, - "attempt_count": 0, - "attempted": false, - "auto_advance": true, - "automatic_tax": { - "enabled": true, - "liability": { - "type": "self" - }, - "status": "complete" - }, - "billing_reason": "subscription_update", - "charge": null, - "collection_method": "send_invoice", - "created": 1718133291, - "currency": "usd", - "custom_fields": [ - { - "name": "Provider", - "value": "MSP" - } - ], - "customer": "cus_QH8QVKyTh2lfcG", - "customer_address": { - "city": null, - "country": "US", - "line1": null, - "line2": null, - "postal_code": "12345", - "state": null - }, - "customer_email": "billing@msp.com", - "customer_name": null, - "customer_phone": null, - "customer_shipping": null, - "customer_tax_exempt": "none", - "customer_tax_ids": [ - ], - "default_payment_method": null, - "default_source": null, - "default_tax_rates": [ - ], - "description": null, - "discount": { - "id": "di_1PQa9eIGBnsLynRrwwYr2bGD", - "object": "discount", - "checkout_session": null, - "coupon": { - "id": "msp-discount-35", - "object": "coupon", - "amount_off": null, - "created": 1678805729, - "currency": null, - "duration": "forever", - "duration_in_months": null, - "livemode": false, - "max_redemptions": null, - "metadata": { - }, - "name": "MSP Discount - 35%", - "percent_off": 35, - "redeem_by": null, - "times_redeemed": 515, - "valid": true, - "percent_off_precise": 35 - }, - "customer": "cus_QH8QVKyTh2lfcG", - "end": null, - "invoice": null, - "invoice_item": null, - "promotion_code": null, - "start": 1718133290, - "subscription": null, - "subscription_item": null - }, - "discounts": [ - "di_1PQa9eIGBnsLynRrwwYr2bGD" - ], - "due_date": 1720725291, - "effective_at": 1718136893, - "ending_balance": 0, - "footer": null, - "from_invoice": null, - "hosted_invoice_url": "https://invoice.stripe.com/i/acct_19smIXIGBnsLynRr/test_YWNjdF8xOXNtSVhJR0Juc0x5blJyLF9RSDhRYVNIejNDMXBMVXAzM0M3S2RwaUt1Z3NuVHVzLDEwODY3NDEyMg0200RT8cC2nw?s=ap", - "invoice_pdf": "https://pay.stripe.com/invoice/acct_19smIXIGBnsLynRr/test_YWNjdF8xOXNtSVhJR0Juc0x5blJyLF9RSDhRYVNIejNDMXBMVXAzM0M3S2RwaUt1Z3NuVHVzLDEwODY3NDEyMg0200RT8cC2nw/pdf?s=ap", - "issuer": { - "type": "self" - }, - "last_finalization_error": null, - "latest_revision": null, - "lines": { - "object": "list", - "data": [ - { - "id": "sub_1PQa9fIGBnsLynRr83lNrFHa", - "object": "line_item", - "amount": 50000, - "amount_excluding_tax": 50000, - "currency": "usd", - "description": null, - "discount_amounts": [ - { - "amount": 17500, - "discount": "di_1PQa9eIGBnsLynRrwwYr2bGD" - } - ], - "discountable": true, - "discounts": [ - ], - "invoice": "in_1PQa9fIGBnsLynRraYIqTdBs", - "livemode": false, - "metadata": { - }, - "period": { - "end": 1720725291, - "start": 1718133291 - }, - "plan": { - "id": "2023-teams-org-seat-monthly", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 500, - "amount_decimal": "500", - "billing_scheme": "per_unit", - "created": 1695839010, - "currency": "usd", - "interval": "month", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "meter": null, - "nickname": "Teams Organization Seat (Monthly)", - "product": "prod_HgOooYXDr2DDAA", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed", - "name": "Password Manager - Teams Plan", - "statement_description": null, - "statement_descriptor": null, - "tiers": null - }, - "price": { - "id": "2023-teams-org-seat-monthly", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1695839010, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "Teams Organization Seat (Monthly)", - "product": "prod_HgOooYXDr2DDAA", - "recurring": { - "aggregate_usage": null, - "interval": "month", - "interval_count": 1, - "meter": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "exclusive", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 500, - "unit_amount_decimal": "500" - }, - "proration": false, - "proration_details": { - "credited_items": null - }, - "quantity": 100, - "subscription": null, - "subscription_item": "si_QH8Qo4WEJxOVwx", - "tax_amounts": [ - { - "amount": 2600, - "inclusive": false, - "tax_rate": "txr_1OZyBuIGBnsLynRrX0PJLuMC", - "taxability_reason": "standard_rated", - "taxable_amount": 32500 - } - ], - "tax_rates": [ - ], - "type": "subscription", - "unit_amount_excluding_tax": "500", - "unique_id": "il_1PQa9fIGBnsLynRrSJ3cxrdU", - "unique_line_item_id": "sli_1acb3eIGBnsLynRr4b9c2f48" - }, - { - "id": "sub_1PQa9fIGBnsLynRr83lNrFHa", - "object": "line_item", - "amount": 70000, - "amount_excluding_tax": 70000, - "currency": "usd", - "description": null, - "discount_amounts": [ - { - "amount": 24500, - "discount": "di_1PQa9eIGBnsLynRrwwYr2bGD" - } - ], - "discountable": true, - "discounts": [ - ], - "invoice": "in_1PQa9fIGBnsLynRraYIqTdBs", - "livemode": false, - "metadata": { - }, - "period": { - "end": 1720725291, - "start": 1718133291 - }, - "plan": { - "id": "2023-enterprise-seat-monthly", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 700, - "amount_decimal": "700", - "billing_scheme": "per_unit", - "created": 1695152194, - "currency": "usd", - "interval": "month", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "meter": null, - "nickname": "Enterprise Organization (Monthly)", - "product": "prod_HgSOgzUlYDFOzf", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed", - "name": "Password Manager - Enterprise Plan", - "statement_description": null, - "statement_descriptor": null, - "tiers": null - }, - "price": { - "id": "2023-enterprise-seat-monthly", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1695152194, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "Enterprise Organization (Monthly)", - "product": "prod_HgSOgzUlYDFOzf", - "recurring": { - "aggregate_usage": null, - "interval": "month", - "interval_count": 1, - "meter": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "exclusive", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 700, - "unit_amount_decimal": "700" - }, - "proration": false, - "proration_details": { - "credited_items": null - }, - "quantity": 100, - "subscription": null, - "subscription_item": "si_QH8QUjtceXvcis", - "tax_amounts": [ - { - "amount": 3640, - "inclusive": false, - "tax_rate": "txr_1OZyBuIGBnsLynRrX0PJLuMC", - "taxability_reason": "standard_rated", - "taxable_amount": 45500 - } - ], - "tax_rates": [ - ], - "type": "subscription", - "unit_amount_excluding_tax": "700", - "unique_id": "il_1PQa9fIGBnsLynRrVviet37m", - "unique_line_item_id": "sli_11b229IGBnsLynRr837b79d0" - } - ], - "has_more": false, - "total_count": 2, - "url": "/v1/invoices/in_1PQa9fIGBnsLynRraYIqTdBs/lines" - }, - "livemode": false, - "metadata": { - }, - "next_payment_attempt": null, - "number": "525EB050-0001", - "on_behalf_of": null, - "paid": false, - "paid_out_of_band": false, - "payment_intent": "pi_3PQaA7IGBnsLynRr1swr9XJE", - "payment_settings": { - "default_mandate": null, - "payment_method_options": null, - "payment_method_types": null - }, - "period_end": 1718133291, - "period_start": 1718133291, - "post_payment_credit_notes_amount": 0, - "pre_payment_credit_notes_amount": 0, - "quote": null, - "receipt_number": null, - "rendering": null, - "rendering_options": null, - "shipping_cost": null, - "shipping_details": null, - "starting_balance": 0, - "statement_descriptor": null, - "status": "open", - "status_transitions": { - "finalized_at": 1718136893, - "marked_uncollectible_at": null, - "paid_at": null, - "voided_at": null - }, - "subscription": "sub_1PQa9fIGBnsLynRr83lNrFHa", - "subscription_details": { - "metadata": { - "providerId": "655bc5a3-2332-4201-a9a6-b18c013d0572" - } - }, - "subtotal": 120000, - "subtotal_excluding_tax": 120000, - "tax": 6240, - "test_clock": "clock_1PQaA4IGBnsLynRrptkZjgxc", - "total": 84240, - "total_discount_amounts": [ - { - "amount": 42000, - "discount": "di_1PQa9eIGBnsLynRrwwYr2bGD" - } - ], - "total_excluding_tax": 78000, - "total_tax_amounts": [ - { - "amount": 6240, - "inclusive": false, - "tax_rate": "txr_1OZyBuIGBnsLynRrX0PJLuMC", - "taxability_reason": "standard_rated", - "taxable_amount": 78000 - } - ], - "transfer_data": null, - "webhooks_delivered_at": 1718133293, - "application_fee": null, - "billing": "send_invoice", - "closed": false, - "date": 1718133291, - "finalized_at": 1718136893, - "forgiven": false, - "payment": null, - "statement_description": null, - "tax_percent": 8 - } - }, - "livemode": false, - "pending_webhooks": 5, - "request": null, - "type": "invoice.finalized", - "user_id": "acct_19smIXIGBnsLynRr" -} diff --git a/test/Billing.Test/Resources/Events/invoice.upcoming.json b/test/Billing.Test/Resources/Events/invoice.upcoming.json deleted file mode 100644 index 1ecf2c616d..0000000000 --- a/test/Billing.Test/Resources/Events/invoice.upcoming.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "id": "evt_1Nv0w8IGBnsLynRrZoDVI44u", - "object": "event", - "api_version": "2024-06-20", - "created": 1695833408, - "data": { - "object": { - "object": "invoice", - "account_country": "US", - "account_name": "Bitwarden Inc.", - "account_tax_ids": null, - "amount_due": 0, - "amount_paid": 0, - "amount_remaining": 0, - "amount_shipping": 0, - "application": null, - "application_fee_amount": null, - "attempt_count": 0, - "attempted": false, - "automatic_tax": { - "enabled": true, - "status": "complete" - }, - "billing_reason": "upcoming", - "charge": null, - "collection_method": "charge_automatically", - "created": 1697128681, - "currency": "usd", - "custom_fields": null, - "customer": "cus_M8DV9wiyNa2JxQ", - "customer_address": { - "city": null, - "country": "US", - "line1": "", - "line2": null, - "postal_code": "90019", - "state": null - }, - "customer_email": "vphan@bitwarden.com", - "customer_name": null, - "customer_phone": null, - "customer_shipping": null, - "customer_tax_exempt": "none", - "customer_tax_ids": [ - ], - "default_payment_method": null, - "default_source": null, - "default_tax_rates": [ - ], - "description": null, - "discount": null, - "discounts": [ - ], - "due_date": null, - "effective_at": null, - "ending_balance": -6779, - "footer": null, - "from_invoice": null, - "last_finalization_error": null, - "latest_revision": null, - "lines": { - "object": "list", - "data": [ - { - "id": "il_tmp_12b5e8IGBnsLynRr1996ac3a", - "object": "line_item", - "amount": 2000, - "amount_excluding_tax": 2000, - "currency": "usd", - "description": "5 × 2019 Enterprise Seat (Monthly) (at $4.00 / month)", - "discount_amounts": [ - ], - "discountable": true, - "discounts": [ - ], - "livemode": false, - "metadata": { - }, - "period": { - "end": 1699807081, - "start": 1697128681 - }, - "plan": { - "id": "enterprise-org-seat-monthly", - "object": "plan", - "active": true, - "aggregate_usage": null, - "amount": 400, - "amount_decimal": "400", - "billing_scheme": "per_unit", - "created": 1494268635, - "currency": "usd", - "interval": "month", - "interval_count": 1, - "livemode": false, - "metadata": { - }, - "nickname": "2019 Enterprise Seat (Monthly)", - "product": "prod_BVButYytPSlgs6", - "tiers_mode": null, - "transform_usage": null, - "trial_period_days": null, - "usage_type": "licensed" - }, - "price": { - "id": "enterprise-org-seat-monthly", - "object": "price", - "active": true, - "billing_scheme": "per_unit", - "created": 1494268635, - "currency": "usd", - "custom_unit_amount": null, - "livemode": false, - "lookup_key": null, - "metadata": { - }, - "nickname": "2019 Enterprise Seat (Monthly)", - "product": "prod_BVButYytPSlgs6", - "recurring": { - "aggregate_usage": null, - "interval": "month", - "interval_count": 1, - "trial_period_days": null, - "usage_type": "licensed" - }, - "tax_behavior": "unspecified", - "tiers_mode": null, - "transform_quantity": null, - "type": "recurring", - "unit_amount": 400, - "unit_amount_decimal": "400" - }, - "proration": false, - "proration_details": { - "credited_items": null - }, - "quantity": 5, - "subscription": "sub_1NQxz4IGBnsLynRr1KbitG7v", - "subscription_item": "si_ODOmLnPDHBuMxX", - "tax_amounts": [ - { - "amount": 0, - "inclusive": false, - "tax_rate": "txr_1N6XCyIGBnsLynRr0LHs4AUD", - "taxability_reason": "product_exempt", - "taxable_amount": 0 - } - ], - "tax_rates": [ - ], - "type": "subscription", - "unit_amount_excluding_tax": "400" - } - ], - "has_more": false, - "total_count": 1, - "url": "/v1/invoices/upcoming/lines?customer=cus_M8DV9wiyNa2JxQ&subscription=sub_1NQxz4IGBnsLynRr1KbitG7v" - }, - "livemode": false, - "metadata": { - }, - "next_payment_attempt": 1697132281, - "number": null, - "on_behalf_of": null, - "paid": false, - "paid_out_of_band": false, - "payment_intent": null, - "payment_settings": { - "default_mandate": null, - "payment_method_options": null, - "payment_method_types": null - }, - "period_end": 1697128681, - "period_start": 1694536681, - "post_payment_credit_notes_amount": 0, - "pre_payment_credit_notes_amount": 0, - "quote": null, - "receipt_number": null, - "rendering": null, - "rendering_options": null, - "shipping_cost": null, - "shipping_details": null, - "starting_balance": -8779, - "statement_descriptor": null, - "status": "draft", - "status_transitions": { - "finalized_at": null, - "marked_uncollectible_at": null, - "paid_at": null, - "voided_at": null - }, - "subscription": "sub_1NQxz4IGBnsLynRr1KbitG7v", - "subscription_details": { - "metadata": { - } - }, - "subtotal": 2000, - "subtotal_excluding_tax": 2000, - "tax": 0, - "test_clock": null, - "total": 2000, - "total_discount_amounts": [ - ], - "total_excluding_tax": 2000, - "total_tax_amounts": [ - { - "amount": 0, - "inclusive": false, - "tax_rate": "txr_1N6XCyIGBnsLynRr0LHs4AUD", - "taxability_reason": "product_exempt", - "taxable_amount": 0 - } - ], - "transfer_data": null, - "webhooks_delivered_at": null - } - }, - "livemode": false, - "pending_webhooks": 5, - "request": { - "id": null, - "idempotency_key": null - }, - "type": "invoice.upcoming" -} diff --git a/test/Billing.Test/Resources/Events/payment_method.attached.json b/test/Billing.Test/Resources/Events/payment_method.attached.json deleted file mode 100644 index 2d22a929d4..0000000000 --- a/test/Billing.Test/Resources/Events/payment_method.attached.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "id": "evt_1NvKzcIGBnsLynRrPJ3hybkd", - "object": "event", - "api_version": "2024-06-20", - "created": 1695910504, - "data": { - "object": { - "id": "pm_1NvKzbIGBnsLynRry6x7Buvc", - "object": "payment_method", - "billing_details": { - "address": { - "city": null, - "country": null, - "line1": null, - "line2": null, - "postal_code": null, - "state": null - }, - "email": null, - "name": null, - "phone": null - }, - "card": { - "brand": "visa", - "checks": { - "address_line1_check": null, - "address_postal_code_check": null, - "cvc_check": "pass" - }, - "country": "US", - "exp_month": 6, - "exp_year": 2033, - "fingerprint": "0VgUBpvqcUUnuSmK", - "funding": "credit", - "generated_from": null, - "last4": "4242", - "networks": { - "available": [ - "visa" - ], - "preferred": null - }, - "three_d_secure_usage": { - "supported": true - }, - "wallet": null - }, - "created": 1695910503, - "customer": "cus_OimYrxnMTMMK1E", - "livemode": false, - "metadata": { - }, - "type": "card" - } - }, - "livemode": false, - "pending_webhooks": 7, - "request": { - "id": "req_2WslNSBD9wAV5v", - "idempotency_key": "db1a648a-3445-47b3-a403-9f3d1303a880" - }, - "type": "payment_method.attached" -} diff --git a/test/Billing.Test/Services/ProviderEventServiceTests.cs b/test/Billing.Test/Services/ProviderEventServiceTests.cs index 7d95157bd2..d5f273fa65 100644 --- a/test/Billing.Test/Services/ProviderEventServiceTests.cs +++ b/test/Billing.Test/Services/ProviderEventServiceTests.cs @@ -1,6 +1,5 @@ using Bit.Billing.Services; using Bit.Billing.Services.Implementations; -using Bit.Billing.Test.Utilities; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.AdminConsole.Repositories; @@ -59,29 +58,69 @@ public class ProviderEventServiceTests public async Task TryRecordInvoiceLineItems_EventTypeNotInvoiceCreatedOrInvoiceFinalized_NoOp() { // Arrange - var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.PaymentMethodAttached); + var stripeEvent = new Event { Type = "payment_method.attached" }; // Act await _providerEventService.TryRecordInvoiceLineItems(stripeEvent); // Assert - await _stripeEventService.DidNotReceiveWithAnyArgs().GetInvoice(Arg.Any()); + await _stripeEventService.DidNotReceiveWithAnyArgs().GetInvoice(Arg.Any(), Arg.Any(), Arg.Any?>()); + } + + [Fact] + public async Task TryRecordInvoiceLineItems_InvoiceParentTypeNotSubscriptionDetails_NoOp() + { + // Arrange + var stripeEvent = new Event + { + Type = "invoice.created" + }; + + var invoice = new Invoice + { + Parent = new InvoiceParent + { + Type = "credit_note", + SubscriptionDetails = new InvoiceParentSubscriptionDetails + { + SubscriptionId = "sub_1" + } + } + }; + + _stripeEventService.GetInvoice(stripeEvent, true, Arg.Any?>()).Returns(invoice); + + // Act + await _providerEventService.TryRecordInvoiceLineItems(stripeEvent); + + // Assert + await _stripeFacade.DidNotReceiveWithAnyArgs().GetSubscription(Arg.Any()); } [Fact] public async Task TryRecordInvoiceLineItems_EventNotProviderRelated_NoOp() { // Arrange - var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.InvoiceCreated); + var stripeEvent = new Event + { + Type = "invoice.created" + }; const string subscriptionId = "sub_1"; var invoice = new Invoice { - SubscriptionId = subscriptionId + Parent = new InvoiceParent + { + Type = "subscription_details", + SubscriptionDetails = new InvoiceParentSubscriptionDetails + { + SubscriptionId = subscriptionId + } + } }; - _stripeEventService.GetInvoice(stripeEvent).Returns(invoice); + _stripeEventService.GetInvoice(stripeEvent, true, Arg.Any?>()).Returns(invoice); var subscription = new Subscription { @@ -101,7 +140,10 @@ public class ProviderEventServiceTests public async Task TryRecordInvoiceLineItems_InvoiceCreated_Succeeds() { // Arrange - var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.InvoiceCreated); + var stripeEvent = new Event + { + Type = "invoice.created" + }; const string subscriptionId = "sub_1"; var providerId = Guid.NewGuid(); @@ -110,17 +152,26 @@ public class ProviderEventServiceTests { Id = "invoice_1", Number = "A", - SubscriptionId = subscriptionId, - Discount = new Discount + Parent = new InvoiceParent { - Coupon = new Coupon + Type = "subscription_details", + SubscriptionDetails = new InvoiceParentSubscriptionDetails { - PercentOff = 35 + SubscriptionId = subscriptionId } - } + }, + Discounts = [ + new Discount + { + Coupon = new Coupon + { + PercentOff = 35 + } + } + ] }; - _stripeEventService.GetInvoice(stripeEvent).Returns(invoice); + _stripeEventService.GetInvoice(stripeEvent, true, Arg.Any?>()).Returns(invoice); var subscription = new Subscription { @@ -249,7 +300,10 @@ public class ProviderEventServiceTests public async Task TryRecordInvoiceLineItems_InvoiceFinalized_Succeeds() { // Arrange - var stripeEvent = await StripeTestEvents.GetAsync(StripeEventType.InvoiceFinalized); + var stripeEvent = new Event + { + Type = "invoice.finalized" + }; const string subscriptionId = "sub_1"; var providerId = Guid.NewGuid(); @@ -258,10 +312,17 @@ public class ProviderEventServiceTests { Id = "invoice_1", Number = "A", - SubscriptionId = subscriptionId + Parent = new InvoiceParent + { + Type = "subscription_details", + SubscriptionDetails = new InvoiceParentSubscriptionDetails + { + SubscriptionId = subscriptionId + } + }, }; - _stripeEventService.GetInvoice(stripeEvent).Returns(invoice); + _stripeEventService.GetInvoice(stripeEvent, true, Arg.Any?>()).Returns(invoice); var subscription = new Subscription { diff --git a/test/Billing.Test/Services/SubscriptionDeletedHandlerTests.cs b/test/Billing.Test/Services/SubscriptionDeletedHandlerTests.cs index 2797b2e589..78dc5aa791 100644 --- a/test/Billing.Test/Services/SubscriptionDeletedHandlerTests.cs +++ b/test/Billing.Test/Services/SubscriptionDeletedHandlerTests.cs @@ -2,6 +2,7 @@ using Bit.Billing.Services; using Bit.Billing.Services.Implementations; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.Billing.Extensions; using Bit.Core.Services; using NSubstitute; using Stripe; @@ -38,7 +39,13 @@ public class SubscriptionDeletedHandlerTests var subscription = new Subscription { Status = "active", - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Metadata = new Dictionary() }; @@ -63,11 +70,14 @@ public class SubscriptionDeletedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Canceled, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), - Metadata = new Dictionary + Items = new StripeList { - { "organizationId", organizationId.ToString() } - } + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, + Metadata = new Dictionary { { "organizationId", organizationId.ToString() } } }; _stripeEventService.GetSubscription(stripeEvent, true).Returns(subscription); @@ -79,7 +89,7 @@ public class SubscriptionDeletedHandlerTests // Assert await _organizationDisableCommand.Received(1) - .DisableAsync(organizationId, subscription.CurrentPeriodEnd); + .DisableAsync(organizationId, subscription.GetCurrentPeriodEnd()); } [Fact] @@ -91,11 +101,14 @@ public class SubscriptionDeletedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Canceled, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), - Metadata = new Dictionary + Items = new StripeList { - { "userId", userId.ToString() } - } + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, + Metadata = new Dictionary { { "userId", userId.ToString() } } }; _stripeEventService.GetSubscription(stripeEvent, true).Returns(subscription); @@ -107,7 +120,7 @@ public class SubscriptionDeletedHandlerTests // Assert await _userService.Received(1) - .DisablePremiumAsync(userId, subscription.CurrentPeriodEnd); + .DisablePremiumAsync(userId, subscription.GetCurrentPeriodEnd()); } [Fact] @@ -119,11 +132,14 @@ public class SubscriptionDeletedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Canceled, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), - Metadata = new Dictionary + Items = new StripeList { - { "organizationId", organizationId.ToString() } + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] }, + Metadata = new Dictionary { { "organizationId", organizationId.ToString() } }, CancellationDetails = new SubscriptionCancellationDetails { Comment = "Cancelled as part of provider migration to Consolidated Billing" @@ -151,11 +167,14 @@ public class SubscriptionDeletedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Canceled, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), - Metadata = new Dictionary + Items = new StripeList { - { "organizationId", organizationId.ToString() } + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] }, + Metadata = new Dictionary { { "organizationId", organizationId.ToString() } }, CancellationDetails = new SubscriptionCancellationDetails { Comment = "Organization was added to Provider" diff --git a/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs b/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs index 6a7cd7704b..16287bc5c9 100644 --- a/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs +++ b/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs @@ -96,7 +96,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "organizationId", organizationId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" } }; @@ -142,7 +148,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Metadata = new Dictionary { ["providerId"] = providerId.ToString(), @@ -206,7 +218,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(30), + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Metadata = new Dictionary { ["providerId"] = providerId.ToString() }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }, TestClock = null @@ -257,6 +275,13 @@ public class SubscriptionUpdatedHandlerTests var subscription = new Subscription { Id = subscriptionId, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Status = StripeSubscriptionStatus.Unpaid, Metadata = new Dictionary { { "providerId", providerId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" } @@ -306,6 +331,13 @@ public class SubscriptionUpdatedHandlerTests var subscription = new Subscription { Id = subscriptionId, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Status = StripeSubscriptionStatus.Unpaid, Metadata = new Dictionary { { "providerId", providerId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" } @@ -348,7 +380,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.IncompleteExpired, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "providerId", providerId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "renewal" } }; @@ -390,7 +428,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "providerId", providerId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" } }; @@ -426,7 +470,13 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "providerId", providerId.ToString() } }, LatestInvoice = new Invoice { BillingReason = "subscription_cycle" } }; @@ -464,13 +514,16 @@ public class SubscriptionUpdatedHandlerTests { Id = subscriptionId, Status = StripeSubscriptionStatus.Unpaid, - CurrentPeriodEnd = currentPeriodEnd, Metadata = new Dictionary { { "userId", userId.ToString() } }, Items = new StripeList { Data = [ - new SubscriptionItem { Price = new Price { Id = IStripeEventUtilityService.PremiumPlanId } } + new SubscriptionItem + { + CurrentPeriodEnd = currentPeriodEnd, + Price = new Price { Id = IStripeEventUtilityService.PremiumPlanId } + } ] } }; @@ -508,7 +561,13 @@ public class SubscriptionUpdatedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Active, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "organizationId", organizationId.ToString() } } }; @@ -552,7 +611,13 @@ public class SubscriptionUpdatedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Active, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "userId", userId.ToString() } } }; @@ -583,7 +648,13 @@ public class SubscriptionUpdatedHandlerTests var subscription = new Subscription { Status = StripeSubscriptionStatus.Active, - CurrentPeriodEnd = currentPeriodEnd, + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + }, Metadata = new Dictionary { { "organizationId", organizationId.ToString() } } }; @@ -616,18 +687,24 @@ public class SubscriptionUpdatedHandlerTests { Id = "sub_123", Status = StripeSubscriptionStatus.Active, - CurrentPeriodEnd = DateTime.UtcNow.AddDays(10), CustomerId = "cus_123", Items = new StripeList { - Data = [new SubscriptionItem { Plan = new Plan { Id = "2023-enterprise-org-seat-annually" } }] + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(10), + Plan = new Plan { Id = "2023-enterprise-org-seat-annually" } + } + ] }, Customer = new Customer { Balance = 0, Discount = new Discount { Coupon = new Coupon { Id = "sm-standalone" } } }, - Discount = new Discount { Coupon = new Coupon { Id = "sm-standalone" } }, + Discounts = [new Discount { Coupon = new Coupon { Id = "sm-standalone" } }], Metadata = new Dictionary { { "organizationId", organizationId.ToString() } } }; @@ -728,7 +805,6 @@ public class SubscriptionUpdatedHandlerTests .IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover); } - [Fact] public async Task HandleAsync_ActiveProviderSubscriptionEvent_AndPreviousSubscriptionStatusWasCanceled_EnableProvider() @@ -998,6 +1074,13 @@ public class SubscriptionUpdatedHandlerTests var newSubscription = new Subscription { Id = previousSubscription?.Id ?? "sub_123", + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) } + ] + }, Status = StripeSubscriptionStatus.Active, Metadata = new Dictionary { { "providerId", providerId.ToString() } } }; @@ -1021,7 +1104,10 @@ public class SubscriptionUpdatedHandlerTests { new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Unpaid } }, new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Incomplete } }, - new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.IncompleteExpired } }, + new object[] + { + new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.IncompleteExpired } + }, new object[] { new Subscription { Id = "sub_123", Status = StripeSubscriptionStatus.Paused } } }; } diff --git a/test/Billing.Test/Utilities/StripeTestEvents.cs b/test/Billing.Test/Utilities/StripeTestEvents.cs deleted file mode 100644 index 86792af812..0000000000 --- a/test/Billing.Test/Utilities/StripeTestEvents.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Stripe; - -namespace Bit.Billing.Test.Utilities; - -public enum StripeEventType -{ - ChargeSucceeded, - CustomerSubscriptionUpdated, - CustomerUpdated, - InvoiceCreated, - InvoiceFinalized, - InvoiceUpcoming, - PaymentMethodAttached -} - -public static class StripeTestEvents -{ - public static async Task GetAsync(StripeEventType eventType) - { - var fileName = eventType switch - { - StripeEventType.ChargeSucceeded => "charge.succeeded.json", - StripeEventType.CustomerSubscriptionUpdated => "customer.subscription.updated.json", - StripeEventType.CustomerUpdated => "customer.updated.json", - StripeEventType.InvoiceCreated => "invoice.created.json", - StripeEventType.InvoiceFinalized => "invoice.finalized.json", - StripeEventType.InvoiceUpcoming => "invoice.upcoming.json", - StripeEventType.PaymentMethodAttached => "payment_method.attached.json" - }; - - var resource = await EmbeddedResourceReader.ReadAsync("Events", fileName); - - return EventUtility.ParseEvent(resource); - } -} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs index 0aa670297b..8f8fd939fe 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs @@ -72,4 +72,65 @@ public class FreeFamiliesForEnterprisePolicyValidatorTests organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.Name); } + + [Theory, BitAutoData] + public async Task ExecutePreUpsertSideEffectAsync_DoesNotNotifyUserWhenPolicyDisabled( + Organization organization, + List organizationSponsorships, + [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, + [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy, + SutProvider sutProvider) + { + policy.Enabled = true; + policyUpdate.Enabled = false; + + sutProvider.GetDependency() + .GetByIdAsync(policyUpdate.OrganizationId) + .Returns(organization); + + sutProvider.GetDependency() + .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) + .Returns(organizationSponsorships); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(default, default, default, default); + } + + [Theory, BitAutoData] + public async Task ExecutePreUpsertSideEffectAsync_DoesNotifyUserWhenPolicyEnabled( + Organization organization, + List organizationSponsorships, + [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, + [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, false)] Policy policy, + SutProvider sutProvider) + { + policy.Enabled = false; + policyUpdate.Enabled = true; + + sutProvider.GetDependency() + .GetByIdAsync(policyUpdate.OrganizationId) + .Returns(organization); + + sutProvider.GetDependency() + .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) + .Returns(organizationSponsorships); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); + + var offerAcceptanceDate = organizationSponsorships[0].ValidUntil!.Value.AddDays(-7).ToString("MM/dd/yyyy"); + await sutProvider.GetDependency() + .Received(1) + .SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync( + organizationSponsorships[0].FriendlyName, + offerAcceptanceDate, + organizationSponsorships[0].SponsoredOrganizationId.ToString(), + organization.Name); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs index a39382382b..a65290e6a7 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs @@ -274,4 +274,176 @@ public class OrganizationDataOwnershipPolicyValidatorTests return sut; } + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_FeatureFlagDisabled_DoesNothing( + [PolicyUpdate(PolicyType.OrganizationDataOwnership, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy, + [Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.CreateDefaultLocation) + .Returns(false); + + var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertDefaultCollectionsAsync(default, default, default); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNothing( + [PolicyUpdate(PolicyType.OrganizationDataOwnership, true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, + [Policy(PolicyType.OrganizationDataOwnership, true)] Policy previousPolicyState, + SutProvider sutProvider) + { + // Arrange + postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; + previousPolicyState.OrganizationId = policyUpdate.OrganizationId; + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.CreateDefaultLocation) + .Returns(true); + + var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertDefaultCollectionsAsync(default, default, default); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothing( + [PolicyUpdate(PolicyType.OrganizationDataOwnership, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy, + [Policy(PolicyType.OrganizationDataOwnership)] Policy previousPolicyState, + SutProvider sutProvider) + { + // Arrange + previousPolicyState.OrganizationId = policyUpdate.OrganizationId; + postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.CreateDefaultLocation) + .Returns(true); + + var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertDefaultCollectionsAsync(default, default, default); + } + + [Theory, BitAutoData] + public async Task ExecutePostUpsertSideEffectAsync_WhenNoUsersExist_DoNothing( + [PolicyUpdate(PolicyType.OrganizationDataOwnership, true)] PolicyUpdate policyUpdate, + [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, + [Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState, + OrganizationDataOwnershipPolicyRequirementFactory factory) + { + // Arrange + postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; + previousPolicyState.OrganizationId = policyUpdate.OrganizationId; + + var policyRepository = ArrangePolicyRepository([]); + var collectionRepository = Substitute.For(); + + var sut = ArrangeSut(factory, policyRepository, collectionRepository); + var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await collectionRepository + .DidNotReceiveWithAnyArgs() + .UpsertDefaultCollectionsAsync( + default, + default, + default); + + await policyRepository + .Received(1) + .GetPolicyDetailsByOrganizationIdAsync( + policyUpdate.OrganizationId, + PolicyType.OrganizationDataOwnership); + } + + [Theory] + [BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))] + public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsertDefaultCollections( + Policy postUpdatedPolicy, + Policy? previousPolicyState, + [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, + [OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable orgPolicyDetails, + OrganizationDataOwnershipPolicyRequirementFactory factory) + { + // Arrange + var orgPolicyDetailsList = orgPolicyDetails.ToList(); + foreach (var policyDetail in orgPolicyDetailsList) + { + policyDetail.OrganizationId = policyUpdate.OrganizationId; + } + + var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList); + var collectionRepository = Substitute.For(); + + var sut = ArrangeSut(factory, policyRepository, collectionRepository); + var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); + + // Act + await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await collectionRepository + .Received(1) + .UpsertDefaultCollectionsAsync( + policyUpdate.OrganizationId, + Arg.Is>(ids => ids.Count() == 3), + _defaultUserCollectionName); + } + + [Theory] + [BitMemberAutoData(nameof(WhenDefaultCollectionsDoesNotExistTestCases))] + public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsInvalid_DoesNothing( + IPolicyMetadataModel metadata, + [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, + [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, + [Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState, + SutProvider sutProvider) + { + // Arrange + postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; + previousPolicyState.OrganizationId = policyUpdate.OrganizationId; + policyUpdate.Enabled = true; + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.CreateDefaultLocation) + .Returns(true); + + var policyRequest = new SavePolicyModel(policyUpdate, null, metadata); + + // Act + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + + // Assert + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertDefaultCollectionsAsync(default, default, default); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs index d3af765f79..857aa5e09e 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs @@ -72,4 +72,66 @@ public class RequireSsoPolicyValidatorTests var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); Assert.True(string.IsNullOrEmpty(result)); } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError( + [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.RequireSso)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = true }; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector }); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); + } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_ValidationError( + [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.RequireSso)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = true }; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption }); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase); + } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_DecryptionOptionsNotEnabled_Success( + [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.RequireSso)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = false }; + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.True(string.IsNullOrEmpty(result)); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs index 83939406b5..cdfd549454 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs @@ -68,4 +68,59 @@ public class ResetPasswordPolicyValidatorTests var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); Assert.True(string.IsNullOrEmpty(result)); } + + [Theory] + [BitAutoData(true, false)] + [BitAutoData(false, true)] + [BitAutoData(false, false)] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_ValidationError( + bool policyEnabled, + bool autoEnrollEnabled, + [PolicyUpdate(PolicyType.ResetPassword)] PolicyUpdate policyUpdate, + [Policy(PolicyType.ResetPassword)] Policy policy, + SutProvider sutProvider) + { + policyUpdate.Enabled = policyEnabled; + policyUpdate.SetDataModel(new ResetPasswordDataModel + { + AutoEnrollEnabled = autoEnrollEnabled + }); + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = true }; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption }); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase); + } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeNotEnabled_Success( + [PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.ResetPassword)] Policy policy, + SutProvider sutProvider) + { + policyUpdate.SetDataModel(new ResetPasswordDataModel + { + AutoEnrollEnabled = false + }); + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = false }; + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.True(string.IsNullOrEmpty(result)); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs index e982a67e46..cea464c155 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; @@ -145,4 +146,135 @@ public class SingleOrgPolicyValidatorTests .Received(1) .SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email); } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError( + [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SingleOrg)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = true }; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector }); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); + } + + [Theory, BitAutoData] + public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorNotEnabled_Success( + [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SingleOrg)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = policyUpdate.OrganizationId; + + var ssoConfig = new SsoConfig { Enabled = false }; + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policyUpdate.OrganizationId) + .Returns(ssoConfig); + + sutProvider.GetDependency() + .HasVerifiedDomainsAsync(policyUpdate.OrganizationId) + .Returns(false); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy); + Assert.True(string.IsNullOrEmpty(result)); + } + + [Theory, BitAutoData] + public async Task ExecutePreUpsertSideEffectAsync_RevokesNonCompliantUsers( + [PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate, + [Policy(PolicyType.SingleOrg, false)] Policy policy, + Guid savingUserId, + Guid nonCompliantUserId, + Organization organization, + SutProvider sutProvider) + { + policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; + + var compliantUser1 = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + OrganizationId = organization.Id, + Type = OrganizationUserType.User, + Status = OrganizationUserStatusType.Confirmed, + UserId = new Guid(), + Email = "user1@example.com" + }; + + var compliantUser2 = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + OrganizationId = organization.Id, + Type = OrganizationUserType.User, + Status = OrganizationUserStatusType.Confirmed, + UserId = new Guid(), + Email = "user2@example.com" + }; + + var nonCompliantUser = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + OrganizationId = organization.Id, + Type = OrganizationUserType.User, + Status = OrganizationUserStatusType.Confirmed, + UserId = nonCompliantUserId, + Email = "user3@example.com" + }; + + sutProvider.GetDependency() + .GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId) + .Returns([compliantUser1, compliantUser2, nonCompliantUser]); + + var otherOrganizationUser = new OrganizationUser + { + Id = Guid.NewGuid(), + OrganizationId = new Guid(), + UserId = nonCompliantUserId, + Status = OrganizationUserStatusType.Confirmed + }; + + sutProvider.GetDependency() + .GetManyByManyUsersAsync(Arg.Is>(ids => ids.Contains(nonCompliantUserId))) + .Returns([otherOrganizationUser]); + + sutProvider.GetDependency().UserId.Returns(savingUserId); + sutProvider.GetDependency().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization); + + sutProvider.GetDependency() + .RevokeNonCompliantOrganizationUsersAsync(Arg.Any()) + .Returns(new CommandResult()); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); + + await sutProvider.GetDependency() + .Received(1) + .RevokeNonCompliantOrganizationUsersAsync( + Arg.Is(r => + r.OrganizationId == organization.Id && + r.OrganizationUsers.Count() == 1 && + r.OrganizationUsers.First().Id == nonCompliantUser.Id)); + await sutProvider.GetDependency() + .DidNotReceive() + .SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email); + await sutProvider.GetDependency() + .DidNotReceive() + .SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email); + await sutProvider.GetDependency() + .Received(1) + .SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs index 7b344d3b29..9eadbcc3b8 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs @@ -136,4 +136,124 @@ public class TwoFactorAuthenticationPolicyValidatorTests .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), compliantUser.Email); } + + [Theory, BitAutoData] + public async Task ExecutePreUpsertSideEffectAsync_GivenNonCompliantUsersWithoutMasterPassword_Throws( + Organization organization, + [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, + [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, + SutProvider sutProvider) + { + policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + + var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.User, + Email = "user3@test.com", + Name = "TEST", + UserId = Guid.NewGuid(), + HasMasterPassword = false + }; + + sutProvider.GetDependency() + .GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId) + .Returns([orgUserDetailUserWithout2Fa]); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>() + { + (orgUserDetailUserWithout2Fa, false), + }); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy)); + + Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message); + } + + [Theory, BitAutoData] + public async Task ExecutePreUpsertSideEffectAsync_RevokesOnlyNonCompliantUsers( + Organization organization, + [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, + [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, + SutProvider sutProvider) + { + // Arrange + policy.OrganizationId = policyUpdate.OrganizationId; + organization.Id = policyUpdate.OrganizationId; + + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + + var nonCompliantUser = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.User, + Email = "user3@test.com", + Name = "TEST", + UserId = Guid.NewGuid(), + HasMasterPassword = true + }; + + var compliantUser = new OrganizationUserUserDetails + { + Id = Guid.NewGuid(), + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.User, + Email = "user4@test.com", + Name = "TEST", + UserId = Guid.NewGuid(), + HasMasterPassword = true + }; + + sutProvider.GetDependency() + .GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId) + .Returns([nonCompliantUser, compliantUser]); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>() + { + (nonCompliantUser, false), + (compliantUser, true) + }); + + sutProvider.GetDependency() + .RevokeNonCompliantOrganizationUsersAsync(Arg.Any()) + .Returns(new CommandResult()); + + var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); + + // Act + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .RevokeNonCompliantOrganizationUsersAsync(Arg.Any()); + + await sutProvider.GetDependency() + .Received(1) + .RevokeNonCompliantOrganizationUsersAsync(Arg.Is(req => + req.OrganizationId == policyUpdate.OrganizationId && + req.OrganizationUsers.SequenceEqual(new[] { nonCompliantUser }) + )); + + await sutProvider.GetDependency() + .Received(1) + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), + nonCompliantUser.Email); + + // Did not send out an email for compliantUser + await sutProvider.GetDependency() + .Received(0) + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), + compliantUser.Email); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs index 1510042446..da10ea300f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs @@ -28,9 +28,10 @@ public class VNextSavePolicyCommandTests // Arrange var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); - var sutProvider = SutProviderFactory( - [new FakeSingleOrgDependencyEvent()], - [fakePolicyValidationEvent]); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); @@ -71,9 +72,10 @@ public class VNextSavePolicyCommandTests // Arrange var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); - var sutProvider = SutProviderFactory( - [new FakeSingleOrgDependencyEvent()], - [fakePolicyValidationEvent]); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); @@ -110,23 +112,6 @@ public class VNextSavePolicyCommandTests p.RevisionDate == revisionDate)); } - [Fact] - public void Constructor_DuplicatePolicyDependencyEvents_Throws() - { - // Arrange & Act - var exception = Assert.Throws(() => - new VNextSavePolicyCommand( - Substitute.For(), - Substitute.For(), - Substitute.For(), - [new FakeSingleOrgDependencyEvent(), new FakeSingleOrgDependencyEvent()], - Substitute.For(), - Substitute.For())); - - // Assert - Assert.Contains("Duplicate PolicyValidationEvent for SingleOrg policy", exception.Message); - } - [Theory, BitAutoData] public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate) { @@ -366,9 +351,10 @@ public class VNextSavePolicyCommandTests // Arrange var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns("Validation error!"); - var sutProvider = SutProviderFactory( - [new FakeSingleOrgDependencyEvent()], - [fakePolicyValidationEvent]); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); @@ -392,20 +378,20 @@ public class VNextSavePolicyCommandTests } /// - /// Returns a new SutProvider with the PolicyDependencyEvents registered in the Sut. + /// Returns a new SutProvider with the PolicyUpdateEvents registered in the Sut. /// private static SutProvider SutProviderFactory( - IEnumerable? policyDependencyEvents = null, - IEnumerable? policyValidationEvents = null) + IEnumerable? policyUpdateEvents = null) { var policyEventHandlerFactory = Substitute.For(); + var handlers = policyUpdateEvents ?? []; // Setup factory to return handlers based on type policyEventHandlerFactory.GetHandler(Arg.Any()) .Returns(callInfo => { var policyType = callInfo.Arg(); - var handler = policyDependencyEvents?.FirstOrDefault(e => e.Type == policyType); + var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); }); @@ -413,7 +399,7 @@ public class VNextSavePolicyCommandTests .Returns(callInfo => { var policyType = callInfo.Arg(); - var handler = policyValidationEvents?.FirstOrDefault(e => e.Type == policyType); + var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); }); @@ -425,7 +411,7 @@ public class VNextSavePolicyCommandTests return new SutProvider() .WithFakeTimeProvider() - .SetDependency(policyDependencyEvents ?? []) + .SetDependency(handlers) .SetDependency(policyEventHandlerFactory) .Create(); } diff --git a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs index 5da0e78422..9c95930c18 100644 --- a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs +++ b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs @@ -467,10 +467,9 @@ public class AuthRequestServiceTests Arg.Any(), Arg.Any()); - var expectedLogMessage = "There are no admin emails to send to."; sutProvider.GetDependency>() .Received(1) - .LogWarning(expectedLogMessage); + .LogWarning("There are no admin emails to send to."); } /// diff --git a/test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs b/test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs index a30e5e896c..65d9e99e3b 100644 --- a/test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs +++ b/test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs @@ -294,7 +294,8 @@ public class InvoiceExtensionsTests Amount = 600 } ); - invoice.Tax = 120; // $1.20 in cents + + invoice.TotalTaxes = [new InvoiceTotalTax { Amount = 120 }]; // $1.20 in cents var subscription = new Subscription(); // Act @@ -318,7 +319,7 @@ public class InvoiceExtensionsTests Amount = 600 } ); - invoice.Tax = null; + invoice.TotalTaxes = []; var subscription = new Subscription(); // Act @@ -341,7 +342,7 @@ public class InvoiceExtensionsTests Amount = 600 } ); - invoice.Tax = 0; + invoice.TotalTaxes = [new InvoiceTotalTax { Amount = 0 }]; var subscription = new Subscription(); // Act @@ -374,7 +375,7 @@ public class InvoiceExtensionsTests var invoice = new Invoice { Lines = lineItems, - Tax = 200 // Additional $2.00 tax + TotalTaxes = [new InvoiceTotalTax { Amount = 200 }] // Additional $2.00 tax }; var subscription = new Subscription(); diff --git a/test/Core.Test/Billing/Models/Business/OrganizationLicenseTests.cs b/test/Core.Test/Billing/Models/Business/OrganizationLicenseTests.cs index b2e94967ce..04b579add3 100644 --- a/test/Core.Test/Billing/Models/Business/OrganizationLicenseTests.cs +++ b/test/Core.Test/Billing/Models/Business/OrganizationLicenseTests.cs @@ -227,8 +227,16 @@ If you believe you need to change the version for a valid reason, please discuss Status = "active", TrialStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), TrialEnd = new DateTime(2024, 2, 1, 0, 0, 0, DateTimeKind.Utc), - CurrentPeriodStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), - CurrentPeriodEnd = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc) + Items = new StripeList + { + Data = [ + new SubscriptionItem + { + CurrentPeriodStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), + CurrentPeriodEnd = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc) + } + ] + } }; return new SubscriptionInfo diff --git a/test/Core.Test/Billing/Models/Business/UserLicenseTests.cs b/test/Core.Test/Billing/Models/Business/UserLicenseTests.cs index 2d1e21b8c5..90bb619ab4 100644 --- a/test/Core.Test/Billing/Models/Business/UserLicenseTests.cs +++ b/test/Core.Test/Billing/Models/Business/UserLicenseTests.cs @@ -141,8 +141,16 @@ If you believe you need to change the version for a valid reason, please discuss Status = "active", TrialStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), TrialEnd = new DateTime(2024, 2, 1, 0, 0, 0, DateTimeKind.Utc), - CurrentPeriodStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), - CurrentPeriodEnd = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc) + Items = new StripeList + { + Data = [ + new SubscriptionItem + { + CurrentPeriodStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), + CurrentPeriodEnd = new DateTime(2024, 12, 31, 0, 0, 0, DateTimeKind.Utc) + } + ] + } }; return new SubscriptionInfo diff --git a/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs b/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs index 08c3d9cf18..8b3a044118 100644 --- a/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs +++ b/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs @@ -54,7 +54,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 500, + TotalTaxes = [new InvoiceTotalTax { Amount = 500 }], Total = 5500 }; @@ -77,7 +77,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2021-family-for-enterprise-annually" && options.SubscriptionDetails.Items[0].Quantity == 1 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -112,7 +112,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 750, + TotalTaxes = [new InvoiceTotalTax { Amount = 750 }], Total = 8250 }; @@ -137,7 +137,9 @@ public class PreviewOrganizationTaxCommandTests item.Price == "2023-teams-org-seat-monthly" && item.Quantity == 5) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-teams-seat-monthly" && item.Quantity == 3) && - options.Coupon == CouponIDs.SecretsManagerStandalone)); + options.Discounts != null && + options.Discounts.Count == 1 && + options.Discounts[0].Coupon == CouponIDs.SecretsManagerStandalone)); } [Fact] @@ -173,7 +175,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 1200, + TotalTaxes = [new InvoiceTotalTax { Amount = 1200 }], Total = 12200 }; @@ -205,7 +207,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "secrets-manager-enterprise-seat-annually" && item.Quantity == 8) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-service-account-2024-annually" && item.Quantity == 3) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -234,7 +236,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 300, + TotalTaxes = [new InvoiceTotalTax { Amount = 300 }], Total = 3300 }; @@ -257,7 +259,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2020-families-org-annually" && options.SubscriptionDetails.Items[0].Quantity == 6 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -286,7 +288,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 0, + TotalTaxes = [new InvoiceTotalTax { Amount = 0 }], Total = 2700 }; @@ -309,7 +311,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-teams-org-seat-monthly" && options.SubscriptionDetails.Items[0].Quantity == 3 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -339,7 +341,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 2100, + TotalTaxes = [new InvoiceTotalTax { Amount = 2100 }], Total = 12100 }; @@ -365,7 +367,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-enterprise-seat-monthly" && options.SubscriptionDetails.Items[0].Quantity == 15 && - options.Coupon == null)); + options.Discounts == null)); } #endregion @@ -399,7 +401,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 120, + TotalTaxes = [new InvoiceTotalTax { Amount = 120 }], Total = 1320 }; @@ -422,7 +424,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-teams-org-seat-monthly" && options.SubscriptionDetails.Items[0].Quantity == 2 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -452,7 +454,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 400, + TotalTaxes = [new InvoiceTotalTax { Amount = 400 }], Total = 4400 }; @@ -475,7 +477,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2020-families-org-annually" && options.SubscriptionDetails.Items[0].Quantity == 1 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -524,7 +526,11 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 900, + TotalTaxes = [new InvoiceTotalTax + { + Amount = 900 + } + ], Total = 9900 }; @@ -546,7 +552,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-teams-org-seat-annually" && options.SubscriptionDetails.Items[0].Quantity == 6 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -595,7 +601,11 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 1200, + TotalTaxes = [new InvoiceTotalTax + { + Amount = 1200 + } + ], Total = 13200 }; @@ -617,7 +627,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-enterprise-org-seat-annually" && options.SubscriptionDetails.Items[0].Quantity == 6 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -647,7 +657,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 800, + TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 8800 }; @@ -672,7 +682,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "2023-enterprise-org-seat-annually" && item.Quantity == 2) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-enterprise-seat-annually" && item.Quantity == 2) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -724,7 +734,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 1500, + TotalTaxes = [new InvoiceTotalTax { Amount = 1500 }], Total = 16500 }; @@ -753,7 +763,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "secrets-manager-enterprise-seat-annually" && item.Quantity == 5) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-service-account-2024-annually" && item.Quantity == 10) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -808,7 +818,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 600, + TotalTaxes = [new InvoiceTotalTax { Amount = 600 }], Total = 6600 }; @@ -831,7 +841,9 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-enterprise-org-seat-annually" && options.SubscriptionDetails.Items[0].Quantity == 5 && - options.Coupon == "EXISTING_DISCOUNT_50")); + options.Discounts != null && + options.Discounts.Count == 1 && + options.Discounts[0].Coupon == "EXISTING_DISCOUNT_50")); } [Fact] @@ -911,7 +923,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 600, + TotalTaxes = [new InvoiceTotalTax { Amount = 600 }], Total = 6600 }; @@ -934,7 +946,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-teams-org-seat-monthly" && options.SubscriptionDetails.Items[0].Quantity == 10 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -976,7 +988,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 1200, + TotalTaxes = [new InvoiceTotalTax { Amount = 1200 }], Total = 13200 }; @@ -1001,7 +1013,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "2023-enterprise-org-seat-annually" && item.Quantity == 15) && options.SubscriptionDetails.Items.Any(item => item.Price == "storage-gb-annually" && item.Quantity == 5) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -1043,7 +1055,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 800, + TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 8800 }; @@ -1066,7 +1078,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "secrets-manager-teams-seat-annually" && options.SubscriptionDetails.Items[0].Quantity == 8 && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -1111,7 +1123,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 1500, + TotalTaxes = [new InvoiceTotalTax { Amount = 1500 }], Total = 16500 }; @@ -1139,7 +1151,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "secrets-manager-enterprise-seat-monthly" && item.Quantity == 12) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-service-account-2024-monthly" && item.Quantity == 20) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -1192,7 +1204,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 2500, + TotalTaxes = [new InvoiceTotalTax { Amount = 2500 }], Total = 27500 }; @@ -1224,7 +1236,9 @@ public class PreviewOrganizationTaxCommandTests item.Price == "secrets-manager-enterprise-seat-annually" && item.Quantity == 15) && options.SubscriptionDetails.Items.Any(item => item.Price == "secrets-manager-service-account-2024-annually" && item.Quantity == 30) && - options.Coupon == "ENTERPRISE_DISCOUNT_20")); + options.Discounts != null && + options.Discounts.Count == 1 && + options.Discounts[0].Coupon == "ENTERPRISE_DISCOUNT_20")); } [Fact] @@ -1266,7 +1280,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 500, + TotalTaxes = [new InvoiceTotalTax { Amount = 500 }], Total = 5500 }; @@ -1291,7 +1305,7 @@ public class PreviewOrganizationTaxCommandTests item.Price == "2020-families-org-annually" && item.Quantity == 6) && options.SubscriptionDetails.Items.Any(item => item.Price == "personal-storage-gb-annually" && item.Quantity == 2) && - options.Coupon == null)); + options.Discounts == null)); } [Fact] @@ -1368,7 +1382,7 @@ public class PreviewOrganizationTaxCommandTests var invoice = new Invoice { - Tax = 300, + TotalTaxes = [new InvoiceTotalTax { Amount = 300 }], Total = 3300 }; @@ -1391,7 +1405,7 @@ public class PreviewOrganizationTaxCommandTests options.SubscriptionDetails.Items.Count == 1 && options.SubscriptionDetails.Items[0].Price == "2023-teams-org-seat-monthly" && options.SubscriptionDetails.Items[0].Quantity == 5 && - options.Coupon == null)); + options.Discounts == null)); } #endregion diff --git a/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs b/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs index 8570dfc6be..ea76f9d975 100644 --- a/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs +++ b/test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs @@ -88,7 +88,7 @@ public class UpdateOrganizationLicenseCommandTests "Hash", "Signature", "SignatureBytes", "InstallationId", "Expires", "ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion", "LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems", - "UseOrganizationDomains", "UseAdminSponsoredFamilies") && + "UseOrganizationDomains", "UseAdminSponsoredFamilies", "UseAutomaticUserConfirmation") && // Same property but different name, use explicit mapping org.ExpirationDate == license.Expires)); } diff --git a/test/Core.Test/Billing/Organizations/Queries/GetCloudOrganizationLicenseQueryTests.cs b/test/Core.Test/Billing/Organizations/Queries/GetCloudOrganizationLicenseQueryTests.cs index ed3698fb1d..617a136fab 100644 --- a/test/Core.Test/Billing/Organizations/Queries/GetCloudOrganizationLicenseQueryTests.cs +++ b/test/Core.Test/Billing/Organizations/Queries/GetCloudOrganizationLicenseQueryTests.cs @@ -27,25 +27,27 @@ public class GetCloudOrganizationLicenseQueryTests { [Theory] [BitAutoData] - public async Task GetLicenseAsync_InvalidInstallationId_Throws(SutProvider sutProvider, + public async Task GetLicenseAsync_InvalidInstallationId_Throws( + SutProvider sutProvider, Organization organization, Guid installationId, int version) { sutProvider.GetDependency().GetByIdAsync(installationId).ReturnsNull(); - var exception = await Assert.ThrowsAsync( - async () => await sutProvider.Sut.GetLicenseAsync(organization, installationId, version)); + var exception = await Assert.ThrowsAsync(async () => + await sutProvider.Sut.GetLicenseAsync(organization, installationId, version)); Assert.Contains("Invalid installation id", exception.Message); } [Theory] [BitAutoData] - public async Task GetLicenseAsync_DisabledOrganization_Throws(SutProvider sutProvider, + public async Task GetLicenseAsync_DisabledOrganization_Throws( + SutProvider sutProvider, Organization organization, Guid installationId, Installation installation) { installation.Enabled = false; sutProvider.GetDependency().GetByIdAsync(installationId).Returns(installation); - var exception = await Assert.ThrowsAsync( - async () => await sutProvider.Sut.GetLicenseAsync(organization, installationId)); + var exception = await Assert.ThrowsAsync(async () => + await sutProvider.Sut.GetLicenseAsync(organization, installationId)); Assert.Contains("Invalid installation id", exception.Message); } @@ -71,7 +73,8 @@ public class GetCloudOrganizationLicenseQueryTests [Theory] [BitAutoData] - public async Task GetLicenseAsync_WhenFeatureFlagEnabled_CreatesToken(SutProvider sutProvider, + public async Task GetLicenseAsync_WhenFeatureFlagEnabled_CreatesToken( + SutProvider sutProvider, Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo, byte[] licenseSignature, string token) { @@ -90,7 +93,8 @@ public class GetCloudOrganizationLicenseQueryTests [Theory] [BitAutoData] - public async Task GetLicenseAsync_MSPManagedOrganization_UsesProviderSubscription(SutProvider sutProvider, + public async Task GetLicenseAsync_MSPManagedOrganization_UsesProviderSubscription( + SutProvider sutProvider, Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo, byte[] licenseSignature, Provider provider) { @@ -99,8 +103,17 @@ public class GetCloudOrganizationLicenseQueryTests subInfo.Subscription = new SubscriptionInfo.BillingSubscription(new Subscription { - CurrentPeriodStart = DateTime.UtcNow, - CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1) + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodStart = DateTime.UtcNow, + CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1) + } + ] + } }); installation.Enabled = true; diff --git a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs new file mode 100644 index 0000000000..21081112d7 --- /dev/null +++ b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs @@ -0,0 +1,369 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Organizations.Models; +using Bit.Core.Billing.Organizations.Queries; +using Bit.Core.Billing.Pricing; +using Bit.Core.Billing.Services; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Stripe; +using Xunit; + +namespace Bit.Core.Test.Billing.Organizations.Queries; + +[SutProviderCustomize] +public class GetOrganizationMetadataQueryTests +{ + [Theory, BitAutoData] + public async Task Run_NullOrganization_ReturnsNull( + SutProvider sutProvider) + { + var result = await sutProvider.Sut.Run(null); + + Assert.Null(result); + } + + [Theory, BitAutoData] + public async Task Run_SelfHosted_ReturnsDefault( + Organization organization, + SutProvider sutProvider) + { + sutProvider.GetDependency().SelfHosted.Returns(true); + + var result = await sutProvider.Sut.Run(organization); + + Assert.Equal(OrganizationMetadata.Default, result); + } + + [Theory, BitAutoData] + public async Task Run_NoGatewaySubscriptionId_ReturnsDefaultWithOccupiedSeats( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = null; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 10, Sponsored = 0 }); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(10, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_NullCustomer_ReturnsDefaultWithOccupiedSeats( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 5, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .ReturnsNull(); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(5, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_NullSubscription_ReturnsDefaultWithOccupiedSeats( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + + var customer = new Customer(); + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 7, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .ReturnsNull(); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(7, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_WithSecretsManagerStandaloneCoupon_ReturnsMetadataWithFlag( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + organization.PlanType = PlanType.EnterpriseAnnually; + + var productId = "product_123"; + var customer = new Customer + { + Discount = new Discount + { + Coupon = new Coupon + { + Id = StripeConstants.CouponIDs.SecretsManagerStandalone, + AppliesTo = new CouponAppliesTo + { + Products = [productId] + } + } + } + }; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = productId + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 15, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .GetPlanOrThrow(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.True(result.IsOnSecretsManagerStandalone); + Assert.Equal(15, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_WithoutSecretsManagerStandaloneCoupon_ReturnsMetadataWithoutFlag( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + organization.PlanType = PlanType.TeamsAnnually; + + var customer = new Customer + { + Discount = null + }; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = "product_123" + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 20, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .GetPlanOrThrow(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(20, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_CouponDoesNotApplyToSubscriptionProducts_ReturnsFalseForStandaloneFlag( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + organization.PlanType = PlanType.EnterpriseAnnually; + + var customer = new Customer + { + Discount = new Discount + { + Coupon = new Coupon + { + Id = StripeConstants.CouponIDs.SecretsManagerStandalone, + AppliesTo = new CouponAppliesTo + { + Products = ["different_product_id"] + } + } + } + }; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = "product_123" + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 12, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .GetPlanOrThrow(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(12, result.OrganizationOccupiedSeats); + } + + [Theory, BitAutoData] + public async Task Run_PlanDoesNotSupportSecretsManager_ReturnsFalseForStandaloneFlag( + Organization organization, + SutProvider sutProvider) + { + organization.GatewaySubscriptionId = "sub_123"; + organization.PlanType = PlanType.FamiliesAnnually; + + var productId = "product_123"; + var customer = new Customer + { + Discount = new Discount + { + Coupon = new Coupon + { + Id = StripeConstants.CouponIDs.SecretsManagerStandalone, + AppliesTo = new CouponAppliesTo + { + Products = [productId] + } + } + } + }; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Plan = new Plan + { + ProductId = productId + } + } + ] + } + }; + + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 8, Sponsored = 0 }); + + sutProvider.GetDependency() + .GetCustomer(organization, Arg.Is(options => + options.Expand.Contains("discount.coupon.applies_to"))) + .Returns(customer); + + sutProvider.GetDependency() + .GetSubscription(organization) + .Returns(subscription); + + sutProvider.GetDependency() + .GetPlanOrThrow(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); + + var result = await sutProvider.Sut.Run(organization); + + Assert.NotNull(result); + Assert.False(result.IsOnSecretsManagerStandalone); + Assert.Equal(8, result.OrganizationOccupiedSeats); + } +} diff --git a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationWarningsQueryTests.cs b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationWarningsQueryTests.cs index 96f9c1496e..05d24bdc34 100644 --- a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationWarningsQueryTests.cs +++ b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationWarningsQueryTests.cs @@ -272,7 +272,16 @@ public class GetOrganizationWarningsQueryTests CollectionMethod = CollectionMethod.SendInvoice, Customer = new Customer(), Status = SubscriptionStatus.Active, - CurrentPeriodEnd = now.AddDays(10), + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = now.AddDays(10) + } + ] + }, TestClock = new TestClock { FrozenTime = now diff --git a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs index e808fb10b0..8504d3122a 100644 --- a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs +++ b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs @@ -1,4 +1,5 @@ using Bit.Core.Billing.Caches; +using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Premium.Commands; using Bit.Core.Billing.Services; @@ -105,6 +106,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -152,6 +163,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -241,7 +262,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; - mockSubscription.CurrentPeriodEnd = DateTime.UtcNow.AddDays(30); + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -286,6 +316,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -326,7 +366,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "incomplete"; - mockSubscription.CurrentPeriodEnd = DateTime.UtcNow.AddDays(30); + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -342,7 +391,7 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests // Assert Assert.True(result.IsT0); Assert.True(user.Premium); - Assert.Equal(mockSubscription.CurrentPeriodEnd, user.PremiumExpirationDate); + Assert.Equal(mockSubscription.GetCurrentPeriodEnd(), user.PremiumExpirationDate); } [Theory, BitAutoData] @@ -368,7 +417,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; - mockSubscription.CurrentPeriodEnd = DateTime.UtcNow.AddDays(30); + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -384,7 +442,7 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests // Assert Assert.True(result.IsT0); Assert.True(user.Premium); - Assert.Equal(mockSubscription.CurrentPeriodEnd, user.PremiumExpirationDate); + Assert.Equal(mockSubscription.GetCurrentPeriodEnd(), user.PremiumExpirationDate); } [Theory, BitAutoData] @@ -411,7 +469,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "active"; // PayPal + active doesn't match pattern - mockSubscription.CurrentPeriodEnd = DateTime.UtcNow.AddDays(30); + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); @@ -453,7 +520,16 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests var mockSubscription = Substitute.For(); mockSubscription.Id = "sub_123"; mockSubscription.Status = "incomplete"; - mockSubscription.CurrentPeriodEnd = DateTime.UtcNow.AddDays(30); + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; var mockInvoice = Substitute.For(); diff --git a/test/Core.Test/Billing/Premium/Commands/PreviewPremiumTaxCommandTests.cs b/test/Core.Test/Billing/Premium/Commands/PreviewPremiumTaxCommandTests.cs index bf7d093dc7..9e919a83f9 100644 --- a/test/Core.Test/Billing/Premium/Commands/PreviewPremiumTaxCommandTests.cs +++ b/test/Core.Test/Billing/Premium/Commands/PreviewPremiumTaxCommandTests.cs @@ -31,7 +31,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 300, + TotalTaxes = [new InvoiceTotalTax { Amount = 300 }], Total = 3300 }; @@ -65,7 +65,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 500, + TotalTaxes = [new InvoiceTotalTax { Amount = 500 }], Total = 5500 }; @@ -101,7 +101,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 250, + TotalTaxes = [new InvoiceTotalTax { Amount = 250 }], Total = 2750 }; @@ -135,7 +135,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 800, + TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 8800 }; @@ -171,7 +171,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 450, + TotalTaxes = [new InvoiceTotalTax { Amount = 450 }], Total = 4950 }; @@ -207,7 +207,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 0, + TotalTaxes = [new InvoiceTotalTax { Amount = 0 }], Total = 3000 }; @@ -241,7 +241,7 @@ public class PreviewPremiumTaxCommandTests var invoice = new Invoice { - Tax = 600, + TotalTaxes = [new InvoiceTotalTax { Amount = 600 }], Total = 6600 }; @@ -276,7 +276,7 @@ public class PreviewPremiumTaxCommandTests // Stripe amounts are in cents var invoice = new Invoice { - Tax = 123, // $1.23 + TotalTaxes = [new InvoiceTotalTax { Amount = 123 }], // $1.23 Total = 3123 // $31.23 }; diff --git a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs index 7edc60a26a..224328d71b 100644 --- a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs +++ b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs @@ -1,10 +1,15 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Models.Sales; +using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Organizations.Services; +using Bit.Core.Billing.Payment.Queries; using Bit.Core.Billing.Pricing; using Bit.Core.Billing.Services; 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; @@ -96,6 +101,10 @@ public class OrganizationBillingServiceTests sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) .Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency() + .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) + .Returns(new OrganizationSeatCounts { Users = 1, Sponsored = 0 }); + var subscriberService = sutProvider.GetDependency(); // Set up subscriber service to return null for customer @@ -110,13 +119,235 @@ public class OrganizationBillingServiceTests Assert.NotNull(metadata); Assert.False(metadata!.IsOnSecretsManagerStandalone); - Assert.False(metadata.HasSubscription); - Assert.False(metadata.IsSubscriptionUnpaid); - Assert.False(metadata.HasOpenInvoice); - Assert.False(metadata.IsSubscriptionCanceled); - Assert.Null(metadata.InvoiceDueDate); - Assert.Null(metadata.InvoiceCreatedDate); - Assert.Null(metadata.SubPeriodEndDate); + Assert.Equal(1, metadata.OrganizationOccupiedSeats); + } + + #endregion + + #region Finalize - Trial Settings + + [Theory, BitAutoData] + public async Task NoPaymentMethodAndTrialPeriod_SetsMissingPaymentMethodCancelBehavior( + Organization organization, + SutProvider sutProvider) + { + // Arrange + var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + organization.PlanType = PlanType.TeamsAnnually; + organization.GatewayCustomerId = "cus_test123"; + organization.GatewaySubscriptionId = null; + + var subscriptionSetup = new SubscriptionSetup + { + PlanType = PlanType.TeamsAnnually, + PasswordManagerOptions = new SubscriptionSetup.PasswordManager + { + Seats = 5, + Storage = null, + PremiumAccess = false + }, + SecretsManagerOptions = null, + SkipTrial = false + }; + + var sale = new OrganizationSale + { + Organization = organization, + SubscriptionSetup = subscriptionSetup + }; + + sutProvider.GetDependency() + .GetPlanOrThrow(PlanType.TeamsAnnually) + .Returns(plan); + + sutProvider.GetDependency() + .Run(organization) + .Returns(false); + + var customer = new Customer + { + Id = "cus_test123", + Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported } + }; + + sutProvider.GetDependency() + .GetCustomerOrThrow(organization, Arg.Any()) + .Returns(customer); + + SubscriptionCreateOptions capturedOptions = null; + sutProvider.GetDependency() + .SubscriptionCreateAsync(Arg.Do(options => capturedOptions = options)) + .Returns(new Subscription + { + Id = "sub_test123", + Status = StripeConstants.SubscriptionStatus.Trialing + }); + + sutProvider.GetDependency() + .ReplaceAsync(organization) + .Returns(Task.CompletedTask); + + // Act + await sutProvider.Sut.Finalize(sale); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .SubscriptionCreateAsync(Arg.Any()); + + Assert.NotNull(capturedOptions); + Assert.Equal(7, capturedOptions.TrialPeriodDays); + Assert.NotNull(capturedOptions.TrialSettings); + Assert.NotNull(capturedOptions.TrialSettings.EndBehavior); + Assert.Equal("cancel", capturedOptions.TrialSettings.EndBehavior.MissingPaymentMethod); + } + + [Theory, BitAutoData] + public async Task NoPaymentMethodButNoTrial_DoesNotSetMissingPaymentMethodBehavior( + Organization organization, + SutProvider sutProvider) + { + // Arrange + var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + organization.PlanType = PlanType.TeamsAnnually; + organization.GatewayCustomerId = "cus_test123"; + organization.GatewaySubscriptionId = null; + + var subscriptionSetup = new SubscriptionSetup + { + PlanType = PlanType.TeamsAnnually, + PasswordManagerOptions = new SubscriptionSetup.PasswordManager + { + Seats = 5, + Storage = null, + PremiumAccess = false + }, + SecretsManagerOptions = null, + SkipTrial = true // This will result in TrialPeriodDays = 0 + }; + + var sale = new OrganizationSale + { + Organization = organization, + SubscriptionSetup = subscriptionSetup + }; + + sutProvider.GetDependency() + .GetPlanOrThrow(PlanType.TeamsAnnually) + .Returns(plan); + + sutProvider.GetDependency() + .Run(organization) + .Returns(false); + + var customer = new Customer + { + Id = "cus_test123", + Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported } + }; + + sutProvider.GetDependency() + .GetCustomerOrThrow(organization, Arg.Any()) + .Returns(customer); + + SubscriptionCreateOptions capturedOptions = null; + sutProvider.GetDependency() + .SubscriptionCreateAsync(Arg.Do(options => capturedOptions = options)) + .Returns(new Subscription + { + Id = "sub_test123", + Status = StripeConstants.SubscriptionStatus.Active + }); + + sutProvider.GetDependency() + .ReplaceAsync(organization) + .Returns(Task.CompletedTask); + + // Act + await sutProvider.Sut.Finalize(sale); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .SubscriptionCreateAsync(Arg.Any()); + + Assert.NotNull(capturedOptions); + Assert.Equal(0, capturedOptions.TrialPeriodDays); + Assert.Null(capturedOptions.TrialSettings); + } + + [Theory, BitAutoData] + public async Task HasPaymentMethodAndTrialPeriod_DoesNotSetMissingPaymentMethodBehavior( + Organization organization, + SutProvider sutProvider) + { + // Arrange + var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + organization.PlanType = PlanType.TeamsAnnually; + organization.GatewayCustomerId = "cus_test123"; + organization.GatewaySubscriptionId = null; + + var subscriptionSetup = new SubscriptionSetup + { + PlanType = PlanType.TeamsAnnually, + PasswordManagerOptions = new SubscriptionSetup.PasswordManager + { + Seats = 5, + Storage = null, + PremiumAccess = false + }, + SecretsManagerOptions = null, + SkipTrial = false + }; + + var sale = new OrganizationSale + { + Organization = organization, + SubscriptionSetup = subscriptionSetup + }; + + sutProvider.GetDependency() + .GetPlanOrThrow(PlanType.TeamsAnnually) + .Returns(plan); + + sutProvider.GetDependency() + .Run(organization) + .Returns(true); // Has payment method + + var customer = new Customer + { + Id = "cus_test123", + Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported } + }; + + sutProvider.GetDependency() + .GetCustomerOrThrow(organization, Arg.Any()) + .Returns(customer); + + SubscriptionCreateOptions capturedOptions = null; + sutProvider.GetDependency() + .SubscriptionCreateAsync(Arg.Do(options => capturedOptions = options)) + .Returns(new Subscription + { + Id = "sub_test123", + Status = StripeConstants.SubscriptionStatus.Trialing + }); + + sutProvider.GetDependency() + .ReplaceAsync(organization) + .Returns(Task.CompletedTask); + + // Act + await sutProvider.Sut.Finalize(sale); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .SubscriptionCreateAsync(Arg.Any()); + + Assert.NotNull(capturedOptions); + Assert.Equal(7, capturedOptions.TrialPeriodDays); + Assert.Null(capturedOptions.TrialSettings); } #endregion diff --git a/test/Core.Test/Billing/Subscriptions/RestartSubscriptionCommandTests.cs b/test/Core.Test/Billing/Subscriptions/RestartSubscriptionCommandTests.cs index a5970c79ab..570f94575f 100644 --- a/test/Core.Test/Billing/Subscriptions/RestartSubscriptionCommandTests.cs +++ b/test/Core.Test/Billing/Subscriptions/RestartSubscriptionCommandTests.cs @@ -88,7 +88,13 @@ public class RestartSubscriptionCommandTests var newSubscription = new Subscription { Id = "sub_new", - CurrentPeriodEnd = currentPeriodEnd + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + } }; _subscriberService.GetSubscription(organization).Returns(existingSubscription); @@ -138,7 +144,13 @@ public class RestartSubscriptionCommandTests var newSubscription = new Subscription { Id = "sub_new", - CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1) + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1) } + ] + } }; _subscriberService.GetSubscription(provider).Returns(existingSubscription); @@ -177,7 +189,13 @@ public class RestartSubscriptionCommandTests var newSubscription = new Subscription { Id = "sub_new", - CurrentPeriodEnd = currentPeriodEnd + Items = new StripeList + { + Data = + [ + new SubscriptionItem { CurrentPeriodEnd = currentPeriodEnd } + ] + } }; _subscriberService.GetSubscription(user).Returns(existingSubscription); diff --git a/test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs b/test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs index 02e04b9ce9..991935b928 100644 --- a/test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs +++ b/test/Core.Test/KeyManagement/Kdf/ChangeKdfCommandTests.cs @@ -1,9 +1,11 @@ #nullable enable using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.KeyManagement.Kdf.Implementations; using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; @@ -21,16 +23,12 @@ public class ChangeKdfCommandTests [BitAutoData] public async Task ChangeKdfAsync_ChangesKdfAsync(SutProvider sutProvider, User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); - sutProvider.GetDependency().UpdatePasswordHash(Arg.Any(), Arg.Any()).Returns(Task.FromResult(IdentityResult.Success)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); + sutProvider.GetDependency().UpdatePasswordHash(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Success)); - var kdf = new KdfSettings - { - KdfType = Enums.KdfType.Argon2id, - Iterations = 4, - Memory = 512, - Parallelism = 4 - }; + var kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 }; var authenticationData = new MasterPasswordAuthenticationData { Kdf = kdf, @@ -59,13 +57,7 @@ public class ChangeKdfCommandTests [BitAutoData] public async Task ChangeKdfAsync_UserIsNull_ThrowsArgumentNullException(SutProvider sutProvider) { - var kdf = new KdfSettings - { - KdfType = Enums.KdfType.Argon2id, - Iterations = 4, - Memory = 512, - Parallelism = 4 - }; + var kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 }; var authenticationData = new MasterPasswordAuthenticationData { Kdf = kdf, @@ -85,17 +77,13 @@ public class ChangeKdfCommandTests [Theory] [BitAutoData] - public async Task ChangeKdfAsync_WrongPassword_ReturnsPasswordMismatch(SutProvider sutProvider, User user) + public async Task ChangeKdfAsync_WrongPassword_ReturnsPasswordMismatch(SutProvider sutProvider, + User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(false)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(false)); - var kdf = new KdfSettings - { - KdfType = Enums.KdfType.Argon2id, - Iterations = 4, - Memory = 512, - Parallelism = 4 - }; + var kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 }; var authenticationData = new MasterPasswordAuthenticationData { Kdf = kdf, @@ -116,7 +104,9 @@ public class ChangeKdfCommandTests [Theory] [BitAutoData] - public async Task ChangeKdfAsync_WithAuthenticationAndUnlockData_UpdatesUserCorrectly(SutProvider sutProvider, User user) + public async Task + ChangeKdfAsync_WithAuthenticationAndUnlockDataAndNoLogoutOnKdfChangeFeatureFlagOff_UpdatesUserCorrectlyAndLogsOut( + SutProvider sutProvider, User user) { var constantKdf = new KdfSettings { @@ -137,8 +127,12 @@ public class ChangeKdfCommandTests MasterKeyWrappedUserKey = "new-wrapped-key", Salt = user.GetMasterPasswordSalt() }; - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); - sutProvider.GetDependency().UpdatePasswordHash(Arg.Any(), Arg.Any()).Returns(Task.FromResult(IdentityResult.Success)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); + sutProvider.GetDependency() + .UpdatePasswordHash(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Success)); + sutProvider.GetDependency().IsEnabled(Arg.Any()).Returns(false); await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData); @@ -150,17 +144,79 @@ public class ChangeKdfCommandTests && u.KdfParallelism == constantKdf.Parallelism && u.Key == "new-wrapped-key" )); + await sutProvider.GetDependency().Received(1).UpdatePasswordHash(user, + authenticationData.MasterPasswordAuthenticationHash, validatePassword: true, refreshStamp: true); + await sutProvider.GetDependency().Received(1).PushLogOutAsync(user.Id); + sutProvider.GetDependency().Received(1).IsEnabled(FeatureFlagKeys.NoLogoutOnKdfChange); } [Theory] [BitAutoData] - public async Task ChangeKdfAsync_KdfNotEqualBetweenAuthAndUnlock_ThrowsBadRequestException(SutProvider sutProvider, User user) + public async Task + ChangeKdfAsync_WithAuthenticationAndUnlockDataAndNoLogoutOnKdfChangeFeatureFlagOn_UpdatesUserCorrectlyAndDoesNotLogOut( + SutProvider sutProvider, User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + var constantKdf = new KdfSettings + { + KdfType = Enums.KdfType.Argon2id, + Iterations = 5, + Memory = 1024, + Parallelism = 4 + }; + var authenticationData = new MasterPasswordAuthenticationData + { + Kdf = constantKdf, + MasterPasswordAuthenticationHash = "new-auth-hash", + Salt = user.GetMasterPasswordSalt() + }; + var unlockData = new MasterPasswordUnlockData + { + Kdf = constantKdf, + MasterKeyWrappedUserKey = "new-wrapped-key", + Salt = user.GetMasterPasswordSalt() + }; + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); + sutProvider.GetDependency() + .UpdatePasswordHash(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(IdentityResult.Success)); + sutProvider.GetDependency().IsEnabled(Arg.Any()).Returns(true); + + await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData); + + await sutProvider.GetDependency().Received(1).ReplaceAsync(Arg.Is(u => + u.Id == user.Id + && u.Kdf == constantKdf.KdfType + && u.KdfIterations == constantKdf.Iterations + && u.KdfMemory == constantKdf.Memory + && u.KdfParallelism == constantKdf.Parallelism + && u.Key == "new-wrapped-key" + )); + await sutProvider.GetDependency().Received(1).UpdatePasswordHash(user, + authenticationData.MasterPasswordAuthenticationHash, validatePassword: true, refreshStamp: false); + await sutProvider.GetDependency().Received(1) + .PushLogOutAsync(user.Id, false, PushNotificationLogOutReason.KdfChange); + await sutProvider.GetDependency().Received(1).PushSyncSettingsAsync(user.Id); + sutProvider.GetDependency().Received(1).IsEnabled(FeatureFlagKeys.NoLogoutOnKdfChange); + } + + [Theory] + [BitAutoData] + public async Task ChangeKdfAsync_KdfNotEqualBetweenAuthAndUnlock_ThrowsBadRequestException( + SutProvider sutProvider, User user) + { + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); var authenticationData = new MasterPasswordAuthenticationData { - Kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 }, + Kdf = new KdfSettings + { + KdfType = Enums.KdfType.Argon2id, + Iterations = 4, + Memory = 512, + Parallelism = 4 + }, MasterPasswordAuthenticationHash = "new-auth-hash", Salt = user.GetMasterPasswordSalt() }; @@ -176,9 +232,11 @@ public class ChangeKdfCommandTests [Theory] [BitAutoData] - public async Task ChangeKdfAsync_AuthDataSaltMismatch_Throws(SutProvider sutProvider, User user, KdfSettings kdf) + public async Task ChangeKdfAsync_AuthDataSaltMismatch_Throws(SutProvider sutProvider, User user, + KdfSettings kdf) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); var authenticationData = new MasterPasswordAuthenticationData { @@ -192,15 +250,17 @@ public class ChangeKdfCommandTests MasterKeyWrappedUserKey = "new-wrapped-key", Salt = user.GetMasterPasswordSalt() }; - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData)); } [Theory] [BitAutoData] - public async Task ChangeKdfAsync_UnlockDataSaltMismatch_Throws(SutProvider sutProvider, User user, KdfSettings kdf) + public async Task ChangeKdfAsync_UnlockDataSaltMismatch_Throws(SutProvider sutProvider, User user, + KdfSettings kdf) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); var authenticationData = new MasterPasswordAuthenticationData { @@ -214,25 +274,22 @@ public class ChangeKdfCommandTests MasterKeyWrappedUserKey = "new-wrapped-key", Salt = "different-salt" }; - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await sutProvider.Sut.ChangeKdfAsync(user, "masterPassword", authenticationData, unlockData)); } [Theory] [BitAutoData] - public async Task ChangeKdfAsync_UpdatePasswordHashFails_ReturnsFailure(SutProvider sutProvider, User user) + public async Task ChangeKdfAsync_UpdatePasswordHashFails_ReturnsFailure(SutProvider sutProvider, + User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); var failedResult = IdentityResult.Failed(new IdentityError { Code = "TestFail", Description = "Test fail" }); - sutProvider.GetDependency().UpdatePasswordHash(Arg.Any(), Arg.Any()).Returns(Task.FromResult(failedResult)); + sutProvider.GetDependency().UpdatePasswordHash(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(failedResult)); - var kdf = new KdfSettings - { - KdfType = Enums.KdfType.Argon2id, - Iterations = 4, - Memory = 512, - Parallelism = 4 - }; + var kdf = new KdfSettings { KdfType = Enums.KdfType.Argon2id, Iterations = 4, Memory = 512, Parallelism = 4 }; var authenticationData = new MasterPasswordAuthenticationData { Kdf = kdf, @@ -253,9 +310,11 @@ public class ChangeKdfCommandTests [Theory] [BitAutoData] - public async Task ChangeKdfAsync_InvalidKdfSettings_ThrowsBadRequestException(SutProvider sutProvider, User user) + public async Task ChangeKdfAsync_InvalidKdfSettings_ThrowsBadRequestException( + SutProvider sutProvider, User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); // Create invalid KDF settings (iterations too low for PBKDF2) var invalidKdf = new KdfSettings @@ -287,9 +346,11 @@ public class ChangeKdfCommandTests [Theory] [BitAutoData] - public async Task ChangeKdfAsync_InvalidArgon2Settings_ThrowsBadRequestException(SutProvider sutProvider, User user) + public async Task ChangeKdfAsync_InvalidArgon2Settings_ThrowsBadRequestException( + SutProvider sutProvider, User user) { - sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()).Returns(Task.FromResult(true)); + sutProvider.GetDependency().CheckPasswordAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(true)); // Create invalid Argon2 KDF settings (memory too high) var invalidKdf = new KdfSettings @@ -318,5 +379,4 @@ public class ChangeKdfCommandTests Assert.Equal("KDF settings are invalid.", exception.Message); } - } diff --git a/test/Core.Test/KeyManagement/Queries/UserAccountKeysQuery.cs b/test/Core.Test/KeyManagement/Queries/UserAccountKeysQuery.cs new file mode 100644 index 0000000000..f79217acba --- /dev/null +++ b/test/Core.Test/KeyManagement/Queries/UserAccountKeysQuery.cs @@ -0,0 +1,43 @@ +using Bit.Core.Entities; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries; +using Bit.Core.KeyManagement.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.KeyManagement.Queries; + +[SutProviderCustomize] +public class UserAccountKeysQueryTests +{ + [Theory, BitAutoData] + public async Task V1User_Success(SutProvider sutProvider, User user) + { + var result = await sutProvider.Sut.Run(user); + Assert.Equal(user.GetPublicKeyEncryptionKeyPair().PublicKey, result.PublicKeyEncryptionKeyPairData.PublicKey); + Assert.Equal(user.GetPublicKeyEncryptionKeyPair().WrappedPrivateKey, result.PublicKeyEncryptionKeyPairData.WrappedPrivateKey); + } + + [Theory, BitAutoData] + public async Task V2User_Success(SutProvider sutProvider, User user) + { + user.SecurityState = "v2"; + user.SecurityVersion = 2; + var signatureKeyPairRepository = sutProvider.GetDependency(); + signatureKeyPairRepository.GetByUserIdAsync(user.Id).Returns(new SignatureKeyPairData(Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519, "wrappedSigningKey", "verifyingKey")); + var result = await sutProvider.Sut.Run(user); + Assert.Equal(user.GetPublicKeyEncryptionKeyPair().PublicKey, result.PublicKeyEncryptionKeyPairData.PublicKey); + Assert.Equal(user.GetPublicKeyEncryptionKeyPair().WrappedPrivateKey, result.PublicKeyEncryptionKeyPairData.WrappedPrivateKey); + Assert.Equal(user.GetPublicKeyEncryptionKeyPair().SignedPublicKey, result.PublicKeyEncryptionKeyPairData.SignedPublicKey); + + Assert.NotNull(result.SignatureKeyPairData); + Assert.Equal("wrappedSigningKey", result.SignatureKeyPairData.WrappedSigningKey); + Assert.Equal("verifyingKey", result.SignatureKeyPairData.VerifyingKey); + + Assert.Equal(user.SecurityState, result.SecurityStateData.SecurityState); + Assert.Equal(user.GetSecurityVersion(), result.SecurityStateData.SecurityVersion); + } + +} diff --git a/test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs b/test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs index e677814fc1..f4d1fc5c94 100644 --- a/test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs +++ b/test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs @@ -1,11 +1,18 @@ using Bit.Core.Entities; +using Bit.Core.KeyManagement.Enums; using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Repositories; using Bit.Core.KeyManagement.UserKey.Implementations; using Bit.Core.Services; +using Bit.Core.Tools.Entities; +using Bit.Core.Tools.Repositories; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Identity; using NSubstitute; +using NSubstitute.ReturnsExtensions; using Xunit; namespace Bit.Core.Test.KeyManagement.UserKey; @@ -14,7 +21,7 @@ namespace Bit.Core.Test.KeyManagement.UserKey; public class RotateUserAccountKeysCommandTests { [Theory, BitAutoData] - public async Task RejectsWrongOldMasterPassword(SutProvider sutProvider, User user, + public async Task RotateUserAccountKeysAsync_WrongOldMasterPassword_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) { user.Email = model.MasterPasswordUnlockData.Email; @@ -25,41 +32,38 @@ public class RotateUserAccountKeysCommandTests Assert.NotEqual(IdentityResult.Success, result); } + [Theory, BitAutoData] - public async Task ThrowsWhenUserIsNull(SutProvider sutProvider, + public async Task RotateUserAccountKeysAsync_UserIsNull_Rejects(SutProvider sutProvider, RotateUserAccountKeysData model) { await Assert.ThrowsAsync(async () => await sutProvider.Sut.RotateUserAccountKeysAsync(null, model)); } + [Theory, BitAutoData] - public async Task RejectsEmailChange(SutProvider sutProvider, User user, + public async Task RotateUserAccountKeysAsync_EmailChange_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) { - user.Kdf = Enums.KdfType.Argon2id; - user.KdfIterations = 3; - user.KdfMemory = 64; - user.KdfParallelism = 4; + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); model.MasterPasswordUnlockData.Email = user.Email + ".different-domain"; - model.MasterPasswordUnlockData.KdfType = Enums.KdfType.Argon2id; - model.MasterPasswordUnlockData.KdfIterations = 3; - model.MasterPasswordUnlockData.KdfMemory = 64; - model.MasterPasswordUnlockData.KdfParallelism = 4; sutProvider.GetDependency().CheckPasswordAsync(user, model.OldMasterKeyAuthenticationHash) .Returns(true); await Assert.ThrowsAsync(async () => await sutProvider.Sut.RotateUserAccountKeysAsync(user, model)); } [Theory, BitAutoData] - public async Task RejectsKdfChange(SutProvider sutProvider, User user, + public async Task RotateUserAccountKeysAsync_KdfChange_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) { - user.Kdf = Enums.KdfType.Argon2id; - user.KdfIterations = 3; - user.KdfMemory = 64; - user.KdfParallelism = 4; + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); - model.MasterPasswordUnlockData.Email = user.Email; model.MasterPasswordUnlockData.KdfType = Enums.KdfType.PBKDF2_SHA256; model.MasterPasswordUnlockData.KdfIterations = 600000; model.MasterPasswordUnlockData.KdfMemory = null; @@ -71,22 +75,15 @@ public class RotateUserAccountKeysCommandTests [Theory, BitAutoData] - public async Task RejectsPublicKeyChange(SutProvider sutProvider, User user, + public async Task RotateUserAccountKeysAsync_PublicKeyChange_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) { - user.PublicKey = "old-public"; - user.Kdf = Enums.KdfType.Argon2id; - user.KdfIterations = 3; - user.KdfMemory = 64; - user.KdfParallelism = 4; - - model.AccountPublicKey = "new-public"; - model.MasterPasswordUnlockData.Email = user.Email; - model.MasterPasswordUnlockData.KdfType = Enums.KdfType.Argon2id; - model.MasterPasswordUnlockData.KdfIterations = 3; - model.MasterPasswordUnlockData.KdfMemory = 64; - model.MasterPasswordUnlockData.KdfParallelism = 4; + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey = "new-public"; sutProvider.GetDependency().CheckPasswordAsync(user, model.OldMasterKeyAuthenticationHash) .Returns(true); @@ -94,27 +91,350 @@ public class RotateUserAccountKeysCommandTests } [Theory, BitAutoData] - public async Task RotatesCorrectly(SutProvider sutProvider, User user, + public async Task RotateUserAccountKeysAsync_V1_Success(SutProvider sutProvider, User user, RotateUserAccountKeysData model) { - user.Kdf = Enums.KdfType.Argon2id; - user.KdfIterations = 3; - user.KdfMemory = 64; - user.KdfParallelism = 4; - - model.MasterPasswordUnlockData.Email = user.Email; - model.MasterPasswordUnlockData.KdfType = Enums.KdfType.Argon2id; - model.MasterPasswordUnlockData.KdfIterations = 3; - model.MasterPasswordUnlockData.KdfMemory = 64; - model.MasterPasswordUnlockData.KdfParallelism = 4; - - model.AccountPublicKey = user.PublicKey; + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); sutProvider.GetDependency().CheckPasswordAsync(user, model.OldMasterKeyAuthenticationHash) .Returns(true); var result = await sutProvider.Sut.RotateUserAccountKeysAsync(user, model); - Assert.Equal(IdentityResult.Success, result); } + + [Theory, BitAutoData] + public async Task RotateUserAccountKeysAsync_UpgradeV1ToV2_Success(SutProvider sutProvider, User user, + RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + + sutProvider.GetDependency().CheckPasswordAsync(user, model.OldMasterKeyAuthenticationHash) + .Returns(true); + + var result = await sutProvider.Sut.RotateUserAccountKeysAsync(user, model); + Assert.Equal(IdentityResult.Success, result); + Assert.Equal(user.SecurityState, model.AccountKeys.SecurityStateData!.SecurityState); + } + + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_PublicKeyChange_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + + model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey = "new-public"; + var saveEncryptedDataActions = new List(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V2User_PrivateKeyNotXChaCha20_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey = "2.xxx"; + + var saveEncryptedDataActions = new List(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V1User_PrivateKeyNotAesCbcHmac_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey = "7.xxx"; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("The provided account private key was not wrapped with AES-256-CBC-HMAC", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V1_Success(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + + var saveEncryptedDataActions = new List(); + await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions); + Assert.Empty(saveEncryptedDataActions); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V2_Success(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + + var saveEncryptedDataActions = new List(); + await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions); + Assert.NotEmpty(saveEncryptedDataActions); + Assert.Equal(user.SecurityState, model.AccountKeys.SecurityStateData!.SecurityState); + } + + + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V2User_VerifyingKeyMismatch_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.SignatureKeyPairData.VerifyingKey = "different-verifying-key"; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("The provided verifying key does not match the user's current verifying key.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V2User_SignedPublicKeyNullOrEmpty_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey = null; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("No signed public key provided, but the user already has a signature key pair.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_V2User_WrappedSigningKeyNotXChaCha20_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.SignatureKeyPairData.WrappedSigningKey = "2.xxx"; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("The provided signing key data is not wrapped with XChaCha20-Poly1305.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeys_UpgradeToV2_InvalidVerifyingKey_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.SignatureKeyPairData.VerifyingKey = ""; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("The provided signature key pair data does not contain a valid verifying key.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_UpgradeToV2_IncorrectlyWrappedPrivateKey_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey = "2.abc"; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("The provided private key encryption key is not wrapped with XChaCha20-Poly1305.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_UpgradeToV2_NoSignedPublicKey_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey = null; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("No signed public key provided, but the user already has a signature key pair.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_UpgradeToV2_NoSecurityState_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.SecurityStateData = null; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("No signed security state provider for V2 user", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_RotateV2_NoSignatureKeyPair_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV2ExistingUser(user, signatureRepository); + SetV2ModelUser(model); + model.AccountKeys.SignatureKeyPairData = null; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("Signature key pair data is required for V2 encryption.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_GetEncryptionType_EmptyString_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey = ""; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("Invalid encryption type string.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateAccountKeysAsync_GetEncryptionType_InvalidString_Rejects(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + SetTestKdfAndSaltForUserAndModel(user, model); + var signatureRepository = sutProvider.GetDependency(); + SetV1ExistingUser(user, signatureRepository); + SetV1ModelUser(model); + model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey = "9.xxx"; + + var saveEncryptedDataActions = new List(); + var ex = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateAccountKeysAsync(model, user, saveEncryptedDataActions)); + Assert.Equal("Invalid encryption type string.", ex.Message); + } + + [Theory, BitAutoData] + public async Task UpdateUserData_RevisionDateChanged_Success(SutProvider sutProvider, User user, RotateUserAccountKeysData model) + { + var oldDate = new DateTime(2017, 1, 1); + + var cipher = Substitute.For(); + cipher.RevisionDate = oldDate; + model.Ciphers = [cipher]; + + var folder = Substitute.For(); + folder.RevisionDate = oldDate; + model.Folders = [folder]; + + var send = Substitute.For(); + send.RevisionDate = oldDate; + model.Sends = [send]; + + var saveEncryptedDataActions = new List(); + + sutProvider.Sut.UpdateUserData(model, user, saveEncryptedDataActions); + foreach (var dataAction in saveEncryptedDataActions) + { + await dataAction.Invoke(); + } + + var updatedCiphers = sutProvider.GetDependency() + .ReceivedCalls() + .FirstOrDefault(call => call.GetMethodInfo().Name == "UpdateForKeyRotation")? + .GetArguments()[1] as IEnumerable; + foreach (var updatedCipher in updatedCiphers!) + { + var oldCipher = model.Ciphers.FirstOrDefault(c => c.Id == updatedCipher.Id); + Assert.NotEqual(oldDate, updatedCipher.RevisionDate); + } + + var updatedFolders = sutProvider.GetDependency() + .ReceivedCalls() + .FirstOrDefault(call => call.GetMethodInfo().Name == "UpdateForKeyRotation")? + .GetArguments()[1] as IEnumerable; + foreach (var updatedFolder in updatedFolders!) + { + var oldFolder = model.Folders.FirstOrDefault(f => f.Id == updatedFolder.Id); + Assert.NotEqual(oldDate, updatedFolder.RevisionDate); + } + + var updatedSends = sutProvider.GetDependency() + .ReceivedCalls() + .FirstOrDefault(call => call.GetMethodInfo().Name == "UpdateForKeyRotation")? + .GetArguments()[1] as IEnumerable; + foreach (var updatedSend in updatedSends!) + { + var oldSend = model.Sends.FirstOrDefault(s => s.Id == updatedSend.Id); + Assert.NotEqual(oldDate, updatedSend.RevisionDate); + } + } + + // Helper functions to set valid test parameters that match each other to the model and user. + private static void SetTestKdfAndSaltForUserAndModel(User user, RotateUserAccountKeysData model) + { + user.Kdf = Enums.KdfType.Argon2id; + user.KdfIterations = 3; + user.KdfMemory = 64; + user.KdfParallelism = 4; + model.MasterPasswordUnlockData.KdfType = Enums.KdfType.Argon2id; + model.MasterPasswordUnlockData.KdfIterations = 3; + model.MasterPasswordUnlockData.KdfMemory = 64; + model.MasterPasswordUnlockData.KdfParallelism = 4; + // The email is the salt for the KDF and is validated currently. + user.Email = model.MasterPasswordUnlockData.Email; + } + + private static void SetV1ExistingUser(User user, IUserSignatureKeyPairRepository userSignatureKeyPairRepository) + { + user.PrivateKey = "2.abc"; + user.PublicKey = "public"; + user.SignedPublicKey = null; + userSignatureKeyPairRepository.GetByUserIdAsync(user.Id).ReturnsNull(); + } + + private static void SetV2ExistingUser(User user, IUserSignatureKeyPairRepository userSignatureKeyPairRepository) + { + user.PrivateKey = "7.abc"; + user.PublicKey = "public"; + user.SignedPublicKey = "signed-public"; + userSignatureKeyPairRepository.GetByUserIdAsync(user.Id).Returns(new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "7.abc", "verifying-key")); + } + + private static void SetV1ModelUser(RotateUserAccountKeysData model) + { + model.AccountKeys.PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("2.abc", "public", null); + model.AccountKeys.SignatureKeyPairData = null; + model.AccountKeys.SecurityStateData = null; + } + + private static void SetV2ModelUser(RotateUserAccountKeysData model) + { + model.AccountKeys.PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData("7.abc", "public", "signed-public"); + model.AccountKeys.SignatureKeyPairData = new SignatureKeyPairData(SignatureAlgorithm.Ed25519, "7.abc", "verifying-key"); + model.AccountKeys.SecurityStateData = new SecurityStateData + { + SecurityState = "abc", + SecurityVersion = 2, + }; + } } diff --git a/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs b/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs index 9c46211517..3f31f1fad4 100644 --- a/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs +++ b/test/Core.Test/Platform/Push/Engines/AzureQueuePushEngineTests.cs @@ -358,20 +358,28 @@ public class AzureQueuePushEngineTests } [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext) + [InlineData(true, null)] + [InlineData(true, PushNotificationLogOutReason.KdfChange)] + [InlineData(false, null)] + [InlineData(false, PushNotificationLogOutReason.KdfChange)] + public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext, + PushNotificationLogOutReason? reason) { var userId = Guid.NewGuid(); + var payload = new JsonObject + { + ["UserId"] = userId + }; + if (reason != null) + { + payload["Reason"] = (int)reason; + } + var expectedPayload = new JsonObject { ["Type"] = 11, - ["Payload"] = new JsonObject - { - ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, - }, + ["Payload"] = payload, }; if (excludeCurrentContext) @@ -380,7 +388,7 @@ public class AzureQueuePushEngineTests } await VerifyNotificationAsync( - async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext), + async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext, reason), expectedPayload ); } diff --git a/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs b/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs index c61c2f37d0..7f230c4e5c 100644 --- a/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs +++ b/test/Core.Test/Platform/Push/Engines/NotificationsApiPushEngineTests.cs @@ -1,6 +1,7 @@ using System.Text.Json.Nodes; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; +using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push.Internal; using Bit.Core.Tools.Entities; @@ -193,7 +194,8 @@ public class NotificationsApiPushEngineTests : PushTestBase }; } - protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext) + protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext, + PushNotificationLogOutReason? reason) { JsonNode? contextId = excludeCurrentContext ? DeviceIdentifier : null; @@ -203,7 +205,7 @@ public class NotificationsApiPushEngineTests : PushTestBase ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + ["Reason"] = reason != null ? (int)reason : null }, ["ContextId"] = contextId, }; diff --git a/test/Core.Test/Platform/Push/Engines/PushTestBase.cs b/test/Core.Test/Platform/Push/Engines/PushTestBase.cs index e0eeeda97d..c0037f57aa 100644 --- a/test/Core.Test/Platform/Push/Engines/PushTestBase.cs +++ b/test/Core.Test/Platform/Push/Engines/PushTestBase.cs @@ -86,7 +86,8 @@ public abstract class PushTestBase protected abstract JsonNode GetPushSyncOrganizationsPayload(Guid userId); protected abstract JsonNode GetPushSyncOrgKeysPayload(Guid userId); protected abstract JsonNode GetPushSyncSettingsPayload(Guid userId); - protected abstract JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext); + protected abstract JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext, + PushNotificationLogOutReason? reason); protected abstract JsonNode GetPushSendCreatePayload(Send send); protected abstract JsonNode GetPushSendUpdatePayload(Send send); protected abstract JsonNode GetPushSendDeletePayload(Send send); @@ -263,15 +264,18 @@ public abstract class PushTestBase } [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext) + [InlineData(true, null)] + [InlineData(true, PushNotificationLogOutReason.KdfChange)] + [InlineData(false, null)] + [InlineData(false, PushNotificationLogOutReason.KdfChange)] + public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext, + PushNotificationLogOutReason? reason) { var userId = Guid.NewGuid(); await VerifyNotificationAsync( - async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext), - GetPushLogOutPayload(userId, excludeCurrentContext) + async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext, reason), + GetPushLogOutPayload(userId, excludeCurrentContext, reason) ); } diff --git a/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs b/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs index 010ad40d13..f8ae07f647 100644 --- a/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs +++ b/test/Core.Test/Platform/Push/Engines/RelayPushEngineTests.cs @@ -4,6 +4,7 @@ using System.Text.Json.Nodes; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; @@ -64,7 +65,7 @@ public class RelayPushNotificationServiceTests : PushTestBase ["UserId"] = cipher.UserId, ["OrganizationId"] = null, // Currently CollectionIds are not passed along from the method signature - // to the request body. + // to the request body. ["CollectionIds"] = null, ["RevisionDate"] = cipher.RevisionDate, }, @@ -88,7 +89,7 @@ public class RelayPushNotificationServiceTests : PushTestBase ["UserId"] = cipher.UserId, ["OrganizationId"] = null, // Currently CollectionIds are not passed along from the method signature - // to the request body. + // to the request body. ["CollectionIds"] = null, ["RevisionDate"] = cipher.RevisionDate, }, @@ -274,7 +275,8 @@ public class RelayPushNotificationServiceTests : PushTestBase }; } - protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext) + protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext, + PushNotificationLogOutReason? reason) { JsonNode? identifier = excludeCurrentContext ? DeviceIdentifier : null; @@ -288,7 +290,7 @@ public class RelayPushNotificationServiceTests : PushTestBase ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + ["Reason"] = reason != null ? (int)reason : null }, ["ClientType"] = null, ["InstallationId"] = null, diff --git a/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs index a32b112675..f5f257c741 100644 --- a/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs +++ b/test/Core.Test/Platform/Push/NotificationHub/NotificationHubPushEngineTests.cs @@ -404,16 +404,18 @@ public class NotificationHubPushNotificationServiceTests } [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task PushLogOutAsync_SendExpectedData(bool excludeCurrentContext) + [InlineData(true, null)] + [InlineData(true, PushNotificationLogOutReason.KdfChange)] + [InlineData(false, null)] + [InlineData(false, PushNotificationLogOutReason.KdfChange)] + public async Task PushLogOutAsync_SendExpectedData(bool excludeCurrentContext, PushNotificationLogOutReason? reason) { var userId = Guid.NewGuid(); var expectedPayload = new JsonObject { ["UserId"] = userId, - ["Date"] = _now, + ["Reason"] = reason != null ? (int)reason : null, }; var expectedTag = excludeCurrentContext @@ -421,7 +423,7 @@ public class NotificationHubPushNotificationServiceTests : $"(template:payload_userId:{userId})"; await VerifyNotificationAsync( - async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext), + async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext, reason), PushType.LogOut, expectedPayload, expectedTag diff --git a/test/Core.Test/Services/StripePaymentServiceTests.cs b/test/Core.Test/Services/StripePaymentServiceTests.cs index 609437b8d1..dd342bd153 100644 --- a/test/Core.Test/Services/StripePaymentServiceTests.cs +++ b/test/Core.Test/Services/StripePaymentServiceTests.cs @@ -18,8 +18,9 @@ public class StripePaymentServiceTests { [Theory] [BitAutoData] - public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithoutAdditionalStorage( - SutProvider sutProvider) + public async Task + PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithoutAdditionalStorage( + SutProvider sutProvider) { var familiesPlan = new FamiliesPlan(); sutProvider.GetDependency() @@ -28,16 +29,13 @@ public class StripePaymentServiceTests var parameters = new PreviewOrganizationInvoiceRequestBody { - PasswordManager = new OrganizationPasswordManagerRequestModel - { - Plan = PlanType.FamiliesAnnually, - AdditionalStorage = 0 - }, - TaxInformation = new TaxInformationRequestModel - { - Country = "FR", - PostalCode = "12345" - } + PasswordManager = + new OrganizationPasswordManagerRequestModel + { + Plan = PlanType.FamiliesAnnually, + AdditionalStorage = 0 + }, + TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" } }; sutProvider.GetDependency() @@ -52,7 +50,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 4000, - Tax = 800, + TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 4800 }); @@ -75,16 +73,13 @@ public class StripePaymentServiceTests var parameters = new PreviewOrganizationInvoiceRequestBody { - PasswordManager = new OrganizationPasswordManagerRequestModel - { - Plan = PlanType.FamiliesAnnually, - AdditionalStorage = 1 - }, - TaxInformation = new TaxInformationRequestModel - { - Country = "FR", - PostalCode = "12345" - } + PasswordManager = + new OrganizationPasswordManagerRequestModel + { + Plan = PlanType.FamiliesAnnually, + AdditionalStorage = 1 + }, + TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" } }; sutProvider.GetDependency() @@ -96,12 +91,7 @@ public class StripePaymentServiceTests p.SubscriptionDetails.Items.Any(x => x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId && x.Quantity == 1))) - .Returns(new Invoice - { - TotalExcludingTax = 4000, - Tax = 800, - Total = 4800 - }); + .Returns(new Invoice { TotalExcludingTax = 4000, TotalTaxes = [new InvoiceTotalTax { Amount = 800 }], Total = 4800 }); var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null); @@ -112,8 +102,9 @@ public class StripePaymentServiceTests [Theory] [BitAutoData] - public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithoutAdditionalStorage( - SutProvider sutProvider) + public async Task + PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithoutAdditionalStorage( + SutProvider sutProvider) { var familiesPlan = new FamiliesPlan(); sutProvider.GetDependency() @@ -128,11 +119,7 @@ public class StripePaymentServiceTests SponsoredPlan = PlanSponsorshipType.FamiliesForEnterprise, AdditionalStorage = 0 }, - TaxInformation = new TaxInformationRequestModel - { - Country = "FR", - PostalCode = "12345" - } + TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" } }; sutProvider.GetDependency() @@ -144,12 +131,7 @@ public class StripePaymentServiceTests p.SubscriptionDetails.Items.Any(x => x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId && x.Quantity == 0))) - .Returns(new Invoice - { - TotalExcludingTax = 0, - Tax = 0, - Total = 0 - }); + .Returns(new Invoice { TotalExcludingTax = 0, TotalTaxes = [new InvoiceTotalTax { Amount = 0 }], Total = 0 }); var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null); @@ -160,8 +142,9 @@ public class StripePaymentServiceTests [Theory] [BitAutoData] - public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithAdditionalStorage( - SutProvider sutProvider) + public async Task + PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithAdditionalStorage( + SutProvider sutProvider) { var familiesPlan = new FamiliesPlan(); sutProvider.GetDependency() @@ -176,11 +159,7 @@ public class StripePaymentServiceTests SponsoredPlan = PlanSponsorshipType.FamiliesForEnterprise, AdditionalStorage = 1 }, - TaxInformation = new TaxInformationRequestModel - { - Country = "FR", - PostalCode = "12345" - } + TaxInformation = new TaxInformationRequestModel { Country = "FR", PostalCode = "12345" } }; sutProvider.GetDependency() @@ -192,12 +171,7 @@ public class StripePaymentServiceTests p.SubscriptionDetails.Items.Any(x => x.Plan == familiesPlan.PasswordManager.StripeStoragePlanId && x.Quantity == 1))) - .Returns(new Invoice - { - TotalExcludingTax = 400, - Tax = 8, - Total = 408 - }); + .Returns(new Invoice { TotalExcludingTax = 400, TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); var actual = await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null); @@ -235,7 +209,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -277,7 +251,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -319,7 +293,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -361,7 +335,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -403,7 +377,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -445,7 +419,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -487,7 +461,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); @@ -529,7 +503,7 @@ public class StripePaymentServiceTests .Returns(new Invoice { TotalExcludingTax = 400, - Tax = 8, + TotalTaxes = [new InvoiceTotalTax { Amount = 8 }], Total = 408 }); diff --git a/test/Identity.Test/AutoFixture/ProfileServiceFixtures.cs b/test/Identity.Test/AutoFixture/ProfileServiceFixtures.cs new file mode 100644 index 0000000000..aaf8b269d6 --- /dev/null +++ b/test/Identity.Test/AutoFixture/ProfileServiceFixtures.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Security.Claims; +using AutoFixture; +using AutoFixture.Xunit2; +using Bit.Core.Auth.Identity; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Validation; + +namespace Bit.Identity.Test.AutoFixture; + +internal class ProfileDataRequestContextCustomization : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customize(composer => composer + .With(o => o.Subject, new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", Guid.NewGuid().ToString()), + new Claim("name", "Test User"), + new Claim("email", "test@example.com") + ]))) + .With(o => o.Client, new Client { ClientId = "web" }) + .With(o => o.ValidatedRequest, () => null) + .With(o => o.RequestedResources, new ResourceValidationResult()) + .With(o => o.IssuedClaims, []) + .Without(o => o.Caller)); + } +} + +public class ProfileDataRequestContextAttribute : CustomizeAttribute +{ + public override ICustomization GetCustomization(ParameterInfo parameter) + { + return new ProfileDataRequestContextCustomization(); + } +} + +internal class IsActiveContextCustomization : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customize(composer => composer + .With(o => o.Subject, new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", Guid.NewGuid().ToString()), + new Claim(Claims.SecurityStamp, "test-security-stamp") + ]))) + .With(o => o.Client, new Client { ClientId = "web" }) + .With(o => o.IsActive, false) + .Without(o => o.Caller)); + } +} + +public class IsActiveContextAttribute : CustomizeAttribute +{ + public override ICustomization GetCustomization(ParameterInfo parameter) + { + return new IsActiveContextCustomization(); + } +} diff --git a/test/Identity.Test/Identity.Test.csproj b/test/Identity.Test/Identity.Test.csproj index fc0cf07b63..496d652b30 100644 --- a/test/Identity.Test/Identity.Test.csproj +++ b/test/Identity.Test/Identity.Test.csproj @@ -5,6 +5,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs index 5a4f83818a..53615cd1d1 100644 --- a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs @@ -10,7 +10,9 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.KeyManagement.Models.Response; +using Bit.Core.KeyManagement.Models.Api.Response; +using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Models.Api; using Bit.Core.Repositories; using Bit.Core.Services; @@ -22,6 +24,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using Duende.IdentityServer.Validation; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using NSubstitute; using Xunit; @@ -40,7 +43,7 @@ public class BaseRequestValidatorTests private readonly IDeviceValidator _deviceValidator; private readonly ITwoFactorAuthenticationValidator _twoFactorAuthenticationValidator; private readonly IOrganizationUserRepository _organizationUserRepository; - private readonly ILogger _logger; + private readonly FakeLogger _logger; private readonly ICurrentContext _currentContext; private readonly GlobalSettings _globalSettings; private readonly IUserRepository _userRepository; @@ -51,6 +54,7 @@ public class BaseRequestValidatorTests private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IAuthRequestRepository _authRequestRepository; private readonly IMailService _mailService; + private readonly IUserAccountKeysQuery _userAccountKeysQuery; private readonly BaseRequestValidatorTestWrapper _sut; @@ -62,7 +66,7 @@ public class BaseRequestValidatorTests _deviceValidator = Substitute.For(); _twoFactorAuthenticationValidator = Substitute.For(); _organizationUserRepository = Substitute.For(); - _logger = Substitute.For>(); + _logger = new FakeLogger(); _currentContext = Substitute.For(); _globalSettings = Substitute.For(); _userRepository = Substitute.For(); @@ -73,6 +77,7 @@ public class BaseRequestValidatorTests _policyRequirementQuery = Substitute.For(); _authRequestRepository = Substitute.For(); _mailService = Substitute.For(); + _userAccountKeysQuery = Substitute.For(); _sut = new BaseRequestValidatorTestWrapper( _userManager, @@ -91,7 +96,8 @@ public class BaseRequestValidatorTests _userDecryptionOptionsBuilder, _policyRequirementQuery, _authRequestRepository, - _mailService); + _mailService, + _userAccountKeysQuery); } /* Logic path @@ -115,7 +121,8 @@ public class BaseRequestValidatorTests await _sut.ValidateAsync(context); // Assert - _logger.Received(1).LogWarning(Constants.BypassFiltersEventId, "Failed login attempt. "); + var logs = _logger.Collector.GetSnapshot(true); + Assert.Contains(logs, l => l.Level == LogLevel.Warning && l.Message == "Failed login attempt. Is2FARequest: False IpAddress: "); var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"]; Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message); } @@ -180,6 +187,13 @@ public class BaseRequestValidatorTests // 5 -> not legacy user _userService.IsLegacyUser(Arg.Any()) .Returns(false); + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); // Act await _sut.ValidateAsync(context); @@ -227,6 +241,13 @@ public class BaseRequestValidatorTests // 5 -> not legacy user _userService.IsLegacyUser(Arg.Any()) .Returns(false); + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); // Act await _sut.ValidateAsync(context); @@ -337,7 +358,7 @@ public class BaseRequestValidatorTests // 1 -> initial validation passes _sut.isValid = true; - // 2 -> enable the FailedTwoFactorEmail feature flag + // 2 -> enable the FailedTwoFactorEmail feature flag _featureService.IsEnabled(FeatureFlagKeys.FailedTwoFactorEmail).Returns(true); // 3 -> set up 2FA as required @@ -460,6 +481,13 @@ public class BaseRequestValidatorTests .Returns(Task.FromResult(new Tuple(false, null))); _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) .Returns(Task.FromResult(true)); + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); await _sut.ValidateAsync(context); @@ -495,6 +523,13 @@ public class BaseRequestValidatorTests _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) .Returns(Task.FromResult(true)); context.ValidatedTokenRequest.ClientId = "web"; + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); // Act await _sut.ValidateAsync(context); @@ -529,6 +564,13 @@ public class BaseRequestValidatorTests _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) .Returns(Task.FromResult(true)); context.ValidatedTokenRequest.ClientId = "web"; + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); // Act await _sut.ValidateAsync(context); @@ -591,6 +633,13 @@ public class BaseRequestValidatorTests HasMasterPassword = false, MasterPasswordUnlock = null })); + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); var context = CreateContext(tokenRequest, requestContext, grantResult); _sut.isValid = true; @@ -644,6 +693,14 @@ public class BaseRequestValidatorTests } })); + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); + var context = CreateContext(tokenRequest, requestContext, grantResult); _sut.isValid = true; @@ -671,6 +728,152 @@ public class BaseRequestValidatorTests Assert.Equal("test@example.com", userDecryptionOptions.MasterPasswordUnlock.Salt); } + [Theory, BitAutoData] + public async Task ValidateAsync_CustomResponse_ShouldIncludeAccountKeys( + [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, + CustomValidatorRequestContext requestContext, + GrantValidationResult grantResult) + { + // Arrange + var mockAccountKeys = new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key", + "test-signed-public-key" + ), + SignatureKeyPairData = new SignatureKeyPairData( + Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519, + "test-wrapped-signing-key", + "test-verifying-key" + ), + SecurityStateData = new SecurityStateData + { + SecurityState = "test-security-state", + SecurityVersion = 2 + } + }; + + _userAccountKeysQuery.Run(Arg.Any()).Returns(mockAccountKeys); + + _userDecryptionOptionsBuilder.ForUser(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithDevice(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithSso(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithWebAuthnLoginCredential(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.BuildAsync().Returns(Task.FromResult(new UserDecryptionOptions + { + HasMasterPassword = true, + MasterPasswordUnlock = new MasterPasswordUnlockResponseModel + { + Kdf = new MasterPasswordUnlockKdfResponseModel + { + KdfType = KdfType.PBKDF2_SHA256, + Iterations = 100000 + }, + MasterKeyEncryptedUserKey = _mockEncryptedString, + Salt = "test@example.com" + } + })); + + var context = CreateContext(tokenRequest, requestContext, grantResult); + _sut.isValid = true; + + _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(requestContext.User, tokenRequest) + .Returns(Task.FromResult(new Tuple(false, null))); + _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) + .Returns(Task.FromResult(true)); + + // Act + await _sut.ValidateAsync(context); + + // Assert + Assert.False(context.GrantResult.IsError); + var customResponse = context.GrantResult.CustomResponse; + + // Verify AccountKeys are included in response + Assert.Contains("AccountKeys", customResponse); + Assert.IsType(customResponse["AccountKeys"]); + + var accountKeysResponse = (PrivateKeysResponseModel)customResponse["AccountKeys"]; + Assert.NotNull(accountKeysResponse.PublicKeyEncryptionKeyPair); + Assert.Equal("test-public-key", accountKeysResponse.PublicKeyEncryptionKeyPair.PublicKey); + Assert.Equal("test-private-key", accountKeysResponse.PublicKeyEncryptionKeyPair.WrappedPrivateKey); + Assert.Equal("test-signed-public-key", accountKeysResponse.PublicKeyEncryptionKeyPair.SignedPublicKey); + + Assert.NotNull(accountKeysResponse.SignatureKeyPair); + Assert.Equal("test-wrapped-signing-key", accountKeysResponse.SignatureKeyPair.WrappedSigningKey); + Assert.Equal("test-verifying-key", accountKeysResponse.SignatureKeyPair.VerifyingKey); + + Assert.NotNull(accountKeysResponse.SecurityState); + Assert.Equal("test-security-state", accountKeysResponse.SecurityState.SecurityState); + Assert.Equal(2, accountKeysResponse.SecurityState.SecurityVersion); + } + [Theory, BitAutoData] + public async Task ValidateAsync_CustomResponse_AccountKeysQuery_SkippedWhenPrivateKeyIsNull( + [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, + CustomValidatorRequestContext requestContext, + GrantValidationResult grantResult) + { + // Arrange + requestContext.User.PrivateKey = null; + + var context = CreateContext(tokenRequest, requestContext, grantResult); + _sut.isValid = true; + _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(requestContext.User, tokenRequest) + .Returns(Task.FromResult(new Tuple(false, null))); + _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) + .Returns(Task.FromResult(true)); + + // Act + await _sut.ValidateAsync(context); + + // Assert + Assert.False(context.GrantResult.IsError); + + // Verify that the account keys query wasn't called. + await _userAccountKeysQuery.Received(0).Run(Arg.Any()); + } + [Theory, BitAutoData] + public async Task ValidateAsync_CustomResponse_AccountKeysQuery_CalledWithCorrectUser( + [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest, + CustomValidatorRequestContext requestContext, + GrantValidationResult grantResult) + { + // Arrange + var expectedUser = requestContext.User; + + _userAccountKeysQuery.Run(Arg.Any()).Returns(new UserAccountKeysData + { + PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData( + "test-private-key", + "test-public-key" + ) + }); + + _userDecryptionOptionsBuilder.ForUser(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithDevice(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithSso(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.WithWebAuthnLoginCredential(Arg.Any()).Returns(_userDecryptionOptionsBuilder); + _userDecryptionOptionsBuilder.BuildAsync().Returns(Task.FromResult(new UserDecryptionOptions())); + + var context = CreateContext(tokenRequest, requestContext, grantResult); + _sut.isValid = true; + + _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(requestContext.User, tokenRequest) + .Returns(Task.FromResult(new Tuple(false, null))); + _deviceValidator.ValidateRequestDeviceAsync(tokenRequest, requestContext) + .Returns(Task.FromResult(true)); + + // Act + await _sut.ValidateAsync(context); + + // Assert + Assert.False(context.GrantResult.IsError); + + // Verify that the account keys query was called with the correct user + await _userAccountKeysQuery.Received(1).Run(Arg.Is(u => u.Id == expectedUser.Id)); + } + private BaseRequestValidationContextFake CreateContext( ValidatedTokenRequest tokenRequest, CustomValidatorRequestContext requestContext, diff --git a/test/Identity.Test/IdentityServer/ProfileServiceTests.cs b/test/Identity.Test/IdentityServer/ProfileServiceTests.cs new file mode 100644 index 0000000000..c467f074ac --- /dev/null +++ b/test/Identity.Test/IdentityServer/ProfileServiceTests.cs @@ -0,0 +1,562 @@ +using System.Security.Claims; +using Bit.Core.AdminConsole.Context; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.Identity; +using Bit.Core.Billing.Services; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Identity.IdentityServer; +using Bit.Test.Common.AutoFixture.Attributes; +using Duende.IdentityServer.Models; +using NSubstitute; +using Xunit; +using AuthFixtures = Bit.Identity.Test.AutoFixture; + +namespace Bit.Identity.Test.IdentityServer; + +public class ProfileServiceTests +{ + private readonly IUserService _userService; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IProviderUserRepository _providerUserRepository; + private readonly IProviderOrganizationRepository _providerOrganizationRepository; + private readonly ILicensingService _licensingService; + private readonly ICurrentContext _currentContext; + private readonly ProfileService _sut; + + public ProfileServiceTests() + { + _userService = Substitute.For(); + _organizationUserRepository = Substitute.For(); + _providerUserRepository = Substitute.For(); + _providerOrganizationRepository = Substitute.For(); + _licensingService = Substitute.For(); + _currentContext = Substitute.For(); + + _sut = new ProfileService( + _userService, + _organizationUserRepository, + _providerUserRepository, + _providerOrganizationRepository, + _licensingService, + _currentContext); + } + + /// + /// For Bitwarden Sends, the zero-knowledge feature architecture is enforced by preserving claims as issued, + /// without attempting user lookup or claims mutation. + /// When acting on behalf of a Send client, the service preserves existing claims, including those issued + /// by the SendAccessGrantValidator, and returns without further claims lookup. + /// + /// + [Theory, BitAutoData] + public async Task GetProfileDataAsync_SendClient_PreservesExistingClaims( + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context) + { + context.Client.ClientId = BitwardenClient.Send; + var existingClaims = new[] + { + new Claim(Claims.SendAccessClaims.SendId, Guid.NewGuid().ToString()), new Claim("send_access", "test") + }; + context.Subject = new ClaimsPrincipal(new ClaimsIdentity(existingClaims)); + + await _sut.GetProfileDataAsync(context); + + Assert.Equal(existingClaims.Length, context.IssuedClaims.Count); + Assert.All(existingClaims, existingClaim => + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type == existingClaim.Type + && issuedClaim.Value == existingClaim.Value)); + } + + /// + /// For Bitwarden Sends, Send access tokens neither represent a user state nor require user profile data. + /// The SendAccessGrantValidator handles validity of requests, including resource passwords and 2FA. + /// Separation of concerns dictates that actions on behalf of Send clients should complete without + /// further lookup of user data. + /// + /// + [Theory, BitAutoData] + public async Task GetProfileDataAsync_SendClient_DoesNotCallUserService( + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context) + { + context.Client.ClientId = BitwardenClient.Send; + + await _sut.GetProfileDataAsync(context); + + await _userService.DidNotReceive().GetUserByPrincipalAsync(Arg.Any()); + } + + /// + /// For Bitwarden Sends, the client is treated as having always-active behavior, and is neither representative of + /// a user state nor requires user profile data. + /// + /// + [Theory, BitAutoData] + public async Task IsActiveAsync_SendClient_ReturnsTrue( + [AuthFixtures.IsActiveContext] IsActiveContext context) + { + context.Client.ClientId = BitwardenClient.Send; + context.IsActive = false; + + await _sut.IsActiveAsync(context); + + Assert.True(context.IsActive); + } + + /// + /// For Bitwarden Sends, the client should not interrogate the user principal as part of evaluating + /// whether it is active. + /// + [Theory, BitAutoData] + public async Task IsActiveAsync_SendClient_DoesNotCallUserService( + [AuthFixtures.IsActiveContext] IsActiveContext context) + { + context.Client.ClientId = BitwardenClient.Send; + + await _sut.IsActiveAsync(context); + + await _userService.DidNotReceive().GetUserByPrincipalAsync(Arg.Any()); + } + + /// + /// When IdentityServer issues a new access token or services a UserInfo request for a given user, + /// re-evaluate the claims for that user to ensure freshness. + /// Organization-specific claims should be filtered out if the user is null for any reason. + /// This allows users to continue acting on their own behalf from a valid authenticated state, but enforces + /// a security boundary which prevents leaking of organization data and ensures organization claims, + /// which are more likely to change than user claims, are accurate and not present if the user cannot be + /// verified. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_UserNull_PreservesExistingNonOrgClaims( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context) + { + context.Client.ClientId = client; + var existingClaims = new[] + { + new Claim("sub", Guid.NewGuid().ToString()), new Claim("email", "test@example.com"), + new Claim(Claims.OrganizationOwner, Guid.NewGuid().ToString()) // This should be filtered out + }; + context.Subject = new ClaimsPrincipal(new ClaimsIdentity(existingClaims)); + _userService.GetUserByPrincipalAsync(context.Subject).Returns((User)null); + + await _sut.GetProfileDataAsync(context); + + // Should preserve user claims + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type == "sub"); + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type == "email"); + // Should filter out organization-related claims + Assert.DoesNotContain(context.IssuedClaims, issuedClaim => issuedClaim.Type.StartsWith("org")); + } + + /// + /// When IdentityServer issues a new access token or services a UserInfo request for a given user, + /// re-evaluate the claims for that user to ensure freshness. + /// New or updated claims, including premium access and organization or provider membership, + /// should be served with the response. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_UserExists_BuildsIdentityClaims( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub")!.Value); + var orgMemberships = new List + { + new() { Id = Guid.NewGuid(), Type = OrganizationUserType.User } + }; + var providerMemberships = new List(); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(true); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(orgMemberships); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(providerMemberships); + + await _sut.GetProfileDataAsync(context); + + Assert.NotEmpty(context.IssuedClaims); + Assert.Contains(context.IssuedClaims, + issuedClaim => issuedClaim.Type == Claims.Premium && + issuedClaim.Value.Equals("true", StringComparison.CurrentCultureIgnoreCase)); + await _licensingService.Received(1).ValidateUserPremiumAsync(user); + await _currentContext.Received(1).OrganizationMembershipAsync(_organizationUserRepository, user.Id); + await _currentContext.Received(1).ProviderMembershipAsync(_providerUserRepository, user.Id); + } + + /// + /// OpenID Connect Core and JWT distinguish between string and boolean types. For spec compliance, + /// boolean types should be served as booleans, not as strings (e.g., true, not "true"). See + /// https://datatracker.ietf.org/doc/html/rfc7159#section-3, and + /// https://datatracker.ietf.org/doc/html/rfc7519#section-2. + /// For proper claims deserialization and type safety, ensure boolean values are treated as + /// ClaimType.Boolean. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_UserExists_BooleanClaimsHaveBooleanType( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub").Value); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(true); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(new List()); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(new List()); + + await _sut.GetProfileDataAsync(context); + + var booleanClaims = context.IssuedClaims.Where(claim => + claim.Value.Equals("true", StringComparison.OrdinalIgnoreCase) || + claim.Value.Equals("false", StringComparison.OrdinalIgnoreCase)); + + Assert.All(booleanClaims, claim => + Assert.Equal(ClaimValueTypes.Boolean, claim.ValueType)); + } + + /// + /// When IdentityServer issues a new access token or services a UserInfo request for a given user, + /// re-evaluate the claims for that user to ensure freshness. + /// Organization-specific claims should never be allowed to persist, and should always be fetched fresh. + /// + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_FiltersOutOrgClaimsFromExisting( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub").Value); + + var existingClaims = new[] + { + new Claim(Claims.OrganizationOwner, Guid.NewGuid().ToString()), + new Claim(Claims.OrganizationAdmin, Guid.NewGuid().ToString()), new Claim("email", "test@example.com"), + new Claim("name", "Test User") + }; + context.Subject = new ClaimsPrincipal(new ClaimsIdentity(existingClaims)); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(false); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(new List()); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(new List()); + + await _sut.GetProfileDataAsync(context); + + Assert.DoesNotContain(context.IssuedClaims, issuedClaim => issuedClaim.Type.StartsWith("org")); + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type == "email"); + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type == "name"); + } + + /// + /// When IdentityServer issues a new access token or services a UserInfo request for a given user, + /// re-evaluate the claims for that user to ensure freshness. + /// Existing claims should always be updated, even if their type exists in the incoming collection. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_NewClaimsOverrideExistingNonOrgClaims( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub").Value); + user.Email = "new@example.com"; + + var existingClaims = new[] + { + new Claim("sub", user.Id.ToString()), new Claim("email", "old@example.com"), + new Claim(Claims.Premium, "false") + }; + context.Subject = new ClaimsPrincipal(new ClaimsIdentity(existingClaims)); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(true); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(new List()); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(new List()); + + await _sut.GetProfileDataAsync(context); + + // Should have new premium claim, not old one + Assert.Contains(context.IssuedClaims, + issuedClaim => issuedClaim.Type == Claims.Premium && + issuedClaim.Value.Equals("true", StringComparison.CurrentCultureIgnoreCase)); + Assert.DoesNotContain(context.IssuedClaims, + issuedClaim => issuedClaim.Type == Claims.Premium && + issuedClaim.Value.Equals("false", StringComparison.CurrentCultureIgnoreCase)); + + // Should have new email + Assert.Contains(context.IssuedClaims, + issuedClaim => issuedClaim.Type == "email" && issuedClaim.Value == "new@example.com"); + Assert.DoesNotContain(context.IssuedClaims, + issuedClaim => issuedClaim.Type == "email" && issuedClaim.Value == "old@example.com"); + } + + /// + /// Users may belong to multiple organizations. Claims should be properly scoped to each relevant organization + /// and not cross-pollinate claims across organizations, and should be fetched fresh on each request. + /// + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_WithMultipleOrganizations_IncludesOrgClaims( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub").Value); + + var orgId1 = Guid.NewGuid(); + var orgId2 = Guid.NewGuid(); + var orgMemberships = new List + { + new() { Id = orgId1, Type = OrganizationUserType.Owner }, + new() { Id = orgId2, Type = OrganizationUserType.Admin } + }; + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(false); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(orgMemberships); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(new List()); + + await _sut.GetProfileDataAsync(context); + + Assert.Contains(context.IssuedClaims, + issuedClaim => issuedClaim.Type == Claims.OrganizationOwner && issuedClaim.Value == orgId1.ToString()); + Assert.Contains(context.IssuedClaims, + issuedClaim => issuedClaim.Type == Claims.OrganizationAdmin && issuedClaim.Value == orgId2.ToString()); + } + + /// + /// Users may belong to providers. Claims should be properly scoped to each relevant provider + /// and not cross-pollinate claims across providers, and should be fetched fresh on each request. + /// + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task GetProfileDataAsync_WithProviders_IncludesProviderClaims( + string client, + [AuthFixtures.ProfileDataRequestContext] + ProfileDataRequestContext context, + User user) + { + context.Client.ClientId = client; + user.Id = Guid.Parse(context.Subject.FindFirst("sub").Value); + + var providerId = Guid.NewGuid(); + var providerMemberships = new List + { + new() { Id = providerId, Type = ProviderUserType.ProviderAdmin } + }; + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + _licensingService.ValidateUserPremiumAsync(user).Returns(false); + _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id) + .Returns(new List()); + _currentContext.ProviderMembershipAsync(_providerUserRepository, user.Id) + .Returns(providerMemberships); + + await _sut.GetProfileDataAsync(context); + + Assert.Contains(context.IssuedClaims, issuedClaim => issuedClaim.Type.StartsWith("provider")); + } + + /// + /// Evaluates the happy path for the core session invalidation mechanism. + /// Critical events (e.g., password change) update the security stamp, and any subsequent request through + /// this service should expose the stamp as invalid. A found user and matching security stamp + /// prove out an active session. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task IsActiveAsync_SecurityStampMatches_ReturnsTrue( + string client, + [AuthFixtures.IsActiveContext] IsActiveContext context, + User user) + { + context.Client.ClientId = client; + var securityStamp = "matching-security-stamp"; + user.SecurityStamp = securityStamp; + + context.Subject = new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", user.Id.ToString()), + new Claim(Claims.SecurityStamp, securityStamp) + ])); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + + await _sut.IsActiveAsync(context); + + Assert.True(context.IsActive); + } + + /// + /// Critical events (e.g., password change) update the security stamp, and any subsequent request through + /// this service should expose the stamp as invalid. + /// See also examples for stamp invalidation (non-exhaustive): + /// + /// + /// + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task IsActiveAsync_SecurityStampDoesNotMatch_ReturnsFalse( + string client, + [AuthFixtures.IsActiveContext] IsActiveContext context, + User user) + { + context.Client.ClientId = client; + user.SecurityStamp = "current-security-stamp"; + + context.Subject = new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", user.Id.ToString()), + new Claim(Claims.SecurityStamp, "old-security-stamp") + ])); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + + await _sut.IsActiveAsync(context); + + Assert.False(context.IsActive); + } + + /// + /// Because security stamps are GUIDs, and database collations, etc., might treat case differently, + /// a case-insensitive comparison is sufficient for proving the match of a security stamp. + /// + [Theory] + [BitAutoData(BitwardenClient.Web, "CuRrEnT-StAmP")] + [BitAutoData(BitwardenClient.Browser, "CuRrEnT-StAmP")] + [BitAutoData(BitwardenClient.Cli, "CuRrEnT-StAmP")] + [BitAutoData(BitwardenClient.Desktop, "CuRrEnT-StAmP")] + [BitAutoData(BitwardenClient.Mobile, "CuRrEnT-StAmP")] + [BitAutoData(BitwardenClient.DirectoryConnector, "CuRrEnT-StAmP")] + public async Task IsActiveAsync_SecurityStampComparison_IsCaseInsensitive( + string client, + string claimStamp, + [AuthFixtures.IsActiveContext] IsActiveContext context, + User user) + { + context.Client.ClientId = client; + user.SecurityStamp = "current-stamp"; + + context.Subject = new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", user.Id.ToString()), + new Claim(Claims.SecurityStamp, claimStamp) + ])); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + + await _sut.IsActiveAsync(context); + + Assert.True(context.IsActive); + } + + /// + /// Security stamps should be evaluated when present, but should not always be expected to be present. + /// Given a successful user lookup, absent a security stamp, the session is treated as active. + /// Only if the stamp is presented on context claims should it be validated. + /// + [Theory] + [BitAutoData(BitwardenClient.Web)] + [BitAutoData(BitwardenClient.Browser)] + [BitAutoData(BitwardenClient.Cli)] + [BitAutoData(BitwardenClient.Desktop)] + [BitAutoData(BitwardenClient.Mobile)] + [BitAutoData(BitwardenClient.DirectoryConnector)] + public async Task IsActiveAsync_UserExistsButNoSecurityStampClaim_ReturnsTrue( + string client, + [AuthFixtures.IsActiveContext] IsActiveContext context, + User user) + { + context.Client.ClientId = client; + context.Subject = new ClaimsPrincipal(new ClaimsIdentity([ + new Claim("sub", user.Id.ToString()), + new Claim("email", user.Email) + ])); + + _userService.GetUserByPrincipalAsync(context.Subject).Returns(user); + + await _sut.IsActiveAsync(context); + + Assert.True(context.IsActive); + } +} diff --git a/test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs b/test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs index db3deedf02..ec3e791d5b 100644 --- a/test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs +++ b/test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs @@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; +using Bit.Core.KeyManagement.Queries.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; @@ -64,7 +65,8 @@ IBaseRequestValidatorTestWrapper IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IPolicyRequirementQuery policyRequirementQuery, IAuthRequestRepository authRequestRepository, - IMailService mailService) : + IMailService mailService, + IUserAccountKeysQuery userAccountKeysQuery) : base( userManager, userService, @@ -82,7 +84,8 @@ IBaseRequestValidatorTestWrapper userDecryptionOptionsBuilder, policyRequirementQuery, authRequestRepository, - mailService) + mailService, + userAccountKeysQuery) { } diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs index 5c7b3ed99d..4e43894a06 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs @@ -92,6 +92,7 @@ public class EfRepositoryListBuilder : ISpecimenBuilder where T : BaseEntityF cfg.AddProfile(); cfg.AddProfile(); cfg.AddProfile(); + cfg.AddProfile(); cfg.AddProfile(); }) .CreateMapper())); diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs index 90a6af51bd..c49b37705c 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs +++ b/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/UserCompare.cs @@ -29,7 +29,8 @@ public class UserCompare : IEqualityComparer x.LicenseKey == y.LicenseKey && x.ApiKey == y.ApiKey && x.Kdf == y.Kdf && - x.KdfIterations == y.KdfIterations; + x.KdfIterations == y.KdfIterations && + x.SignedPublicKey == y.SignedPublicKey; } public int GetHashCode([DisallowNull] User obj) diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs index 2aee528260..4d5f99f846 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs @@ -69,6 +69,42 @@ public static class OrganizationTestHelpers Type = OrganizationUserType.Owner }); + public static Task CreateAcceptedTestOrganizationUserAsync( + this IOrganizationUserRepository organizationUserRepository, + Organization organization, + User user) + => organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Accepted, + Type = OrganizationUserType.Owner + }); + + public static Task CreateRevokedTestOrganizationUserAsync( + this IOrganizationUserRepository organizationUserRepository, + Organization organization, + User user) + => organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Revoked, + Type = OrganizationUserType.Owner + }); + + public static Task CreateConfirmedTestOrganizationUserAsync( + this IOrganizationUserRepository organizationUserRepository, + Organization organization, + User user) + => organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.Owner + }); + public static Task CreateTestGroupAsync( this IGroupRepository groupRepository, Organization organization, @@ -81,9 +117,9 @@ public static class OrganizationTestHelpers this ICollectionRepository collectionRepository, Organization organization, string identifier = "test") - => collectionRepository.CreateAsync(new Collection - { - OrganizationId = organization.Id, - Name = $"{identifier} {Guid.NewGuid()}" - }); + => collectionRepository.CreateAsync(new Collection + { + OrganizationId = organization.Id, + Name = $"{identifier} {Guid.NewGuid()}" + }); } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs index 042d354a87..a60a8e046c 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs @@ -1417,4 +1417,146 @@ public class OrganizationUserRepositoryTests // Regular collection should be removed Assert.DoesNotContain(actualCollections, c => c.Id == regularCollection.Id); } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_WhenUserIsAccepted_ReturnsTrue(IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IUserRepository userRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var user = await userRepository.CreateTestUserAsync(); + var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user); + + // Act + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + + // Assert + Assert.True(result); + var updatedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id); + Assert.NotNull(updatedUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, updatedUser.Status); + + // Annul + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_WhenUserIsInvited_ReturnsFalse(IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserInviteAsync(organization); + + // Act + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + + // Assert + Assert.False(result); + var unchangedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id); + Assert.NotNull(unchangedUser); + Assert.Equal(OrganizationUserStatusType.Invited, unchangedUser.Status); + + // Annul + await organizationRepository.DeleteAsync(organization); + } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_WhenUserIsAlreadyConfirmed_ReturnsFalse(IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IUserRepository userRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var user = await userRepository.CreateTestUserAsync(); + var orgUser = await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(organization, user); + + // Act + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + + // Assert + Assert.False(result); + var unchangedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id); + Assert.NotNull(unchangedUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, unchangedUser.Status); + + // Annul + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_WhenUserIsRevoked_ReturnsFalse(IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IUserRepository userRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var user = await userRepository.CreateTestUserAsync(); + var orgUser = await organizationUserRepository.CreateRevokedTestOrganizationUserAsync(organization, user); + + // Act + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + + // Assert + Assert.False(result); + var unchangedUser = await organizationUserRepository.GetByIdAsync(orgUser.Id); + Assert.NotNull(unchangedUser); + Assert.Equal(OrganizationUserStatusType.Revoked, unchangedUser.Status); + + // Annul + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_IsIdempotent_WhenCalledMultipleTimes( + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IUserRepository userRepository) + { + // Arrange + var organization = await organizationRepository.CreateTestOrganizationAsync(); + var user = await userRepository.CreateTestUserAsync(); + var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user); + + // Act - First call should confirm + var firstResult = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + var secondResult = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + + // Assert + Assert.True(firstResult); + Assert.False(secondResult); + var finalUser = await organizationUserRepository.GetByIdAsync(orgUser.Id); + Assert.NotNull(finalUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, finalUser.Status); + + // Annul + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + + [Theory, DatabaseData] + public async Task ConfirmOrganizationUserAsync_WhenUserDoesNotExist_ReturnsFalse( + IOrganizationUserRepository organizationUserRepository) + { + // Arrange + var nonExistentUser = new OrganizationUser + { + Id = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), + UserId = Guid.NewGuid(), + Email = "nonexistent@bitwarden.com", + Status = OrganizationUserStatusType.Accepted, + Type = OrganizationUserType.Owner + }; + + // Act + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(nonExistentUser); + + // Assert + Assert.False(result); + } } diff --git a/util/Migrator/DbMigrator.cs b/util/Migrator/DbMigrator.cs index e5e7a569b2..9345a72fc2 100644 --- a/util/Migrator/DbMigrator.cs +++ b/util/Migrator/DbMigrator.cs @@ -53,7 +53,7 @@ public class DbMigrator if (ex.Message.Contains("Server is in script upgrade mode.")) { attempt++; - _logger.LogInformation($"Database is in script upgrade mode, trying again (attempt #{attempt})."); + _logger.LogInformation("Database is in script upgrade mode, trying again (attempt #{Attempt}).", attempt); Thread.Sleep(20000); } else @@ -165,7 +165,7 @@ public class DbMigrator { stringBuilder.AppendLine(script.Name); } - _logger.LogInformation(Constants.BypassFiltersEventId, stringBuilder.ToString()); + _logger.LogInformation(Constants.BypassFiltersEventId, "{Scripts}", stringBuilder.ToString()); return true; } diff --git a/util/Migrator/DbScripts/2025-09-26_00_SM_AddSecretVersioningTable.sql b/util/Migrator/DbScripts/2025-09-26_00_SM_AddSecretVersioningTable.sql new file mode 100644 index 0000000000..c039553ce6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-09-26_00_SM_AddSecretVersioningTable.sql @@ -0,0 +1,83 @@ +SET ANSI_NULLS ON; +SET QUOTED_IDENTIFIER ON; +GO + +IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = 'SecretVersion' AND schema_id = SCHEMA_ID('dbo')) +BEGIN + CREATE TABLE [dbo].[SecretVersion] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [SecretId] UNIQUEIDENTIFIER NOT NULL, + [Value] NVARCHAR (MAX) NOT NULL, + [VersionDate] DATETIME2 (7) NOT NULL, + [EditorServiceAccountId] UNIQUEIDENTIFIER NULL, + [EditorOrganizationUserId] UNIQUEIDENTIFIER NULL, + CONSTRAINT [PK_SecretVersion] PRIMARY KEY CLUSTERED ([Id] ASC) + ); +END +GO + +-- Ensure foreign keys exist +IF NOT EXISTS ( + SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_SecretVersion_OrganizationUser' +) +BEGIN + ALTER TABLE [dbo].[SecretVersion] + ADD CONSTRAINT [FK_SecretVersion_OrganizationUser] + FOREIGN KEY ([EditorOrganizationUserId]) + REFERENCES [dbo].[OrganizationUser] ([Id]) + ON DELETE SET NULL; +END +GO + +IF NOT EXISTS ( + SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_SecretVersion_Secret' +) +BEGIN + ALTER TABLE [dbo].[SecretVersion] + ADD CONSTRAINT [FK_SecretVersion_Secret] + FOREIGN KEY ([SecretId]) + REFERENCES [dbo].[Secret] ([Id]) + ON DELETE CASCADE; +END +GO + +IF NOT EXISTS ( + SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_SecretVersion_ServiceAccount' +) +BEGIN + ALTER TABLE [dbo].[SecretVersion] + ADD CONSTRAINT [FK_SecretVersion_ServiceAccount] + FOREIGN KEY ([EditorServiceAccountId]) + REFERENCES [dbo].[ServiceAccount] ([Id]) + ON DELETE SET NULL; +END +GO + +IF NOT EXISTS ( + SELECT 1 FROM sys.indexes WHERE name = 'IX_SecretVersion_SecretId' AND object_id = OBJECT_ID('[dbo].[SecretVersion]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_SecretVersion_SecretId] + ON [dbo].[SecretVersion]([SecretId] ASC); +END +GO + +IF NOT EXISTS ( + SELECT 1 FROM sys.indexes WHERE name = 'IX_SecretVersion_EditorServiceAccountId' AND object_id = OBJECT_ID('[dbo].[SecretVersion]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_SecretVersion_EditorServiceAccountId] + ON [dbo].[SecretVersion]([EditorServiceAccountId] ASC) + WHERE [EditorServiceAccountId] IS NOT NULL; +END +GO + +IF NOT EXISTS ( + SELECT 1 FROM sys.indexes WHERE name = 'IX_SecretVersion_EditorOrganizationUserId' AND object_id = OBJECT_ID('[dbo].[SecretVersion]') +) +BEGIN + CREATE NONCLUSTERED INDEX [IX_SecretVersion_EditorOrganizationUserId] + ON [dbo].[SecretVersion]([EditorOrganizationUserId] ASC) + WHERE [EditorOrganizationUserId] IS NOT NULL; +END +GO diff --git a/util/Migrator/DbScripts/2025-10-07_00_AddAutoConfirmUser.sql b/util/Migrator/DbScripts/2025-10-07_00_AddAutoConfirmUser.sql new file mode 100644 index 0000000000..db6e73e979 --- /dev/null +++ b/util/Migrator/DbScripts/2025-10-07_00_AddAutoConfirmUser.sql @@ -0,0 +1,509 @@ +-- Add AutomaticallyConfirmUsers column to Organization table +IF COL_LENGTH('[dbo].[Organization]', 'UseAutomaticUserConfirmation') IS NULL + BEGIN + ALTER TABLE [dbo].[Organization] + ADD [UseAutomaticUserConfirmation] BIT NOT NULL CONSTRAINT [DF_Organization_UseAutomaticUserConfirmation] DEFAULT (0); + END +GO + +-- Update Organization_Create stored procedure +CREATE OR ALTER PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT= null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = NULL, + @LimitCollectionDeletion BIT = NULL, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0, + @UseOrganizationDomains BIT = 0, + @UseAdminSponsoredFamilies BIT = 0, + @SyncSeats BIT = 0, + @UseAutomaticUserConfirmation BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Identifier], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UsePolicies], + [UseSso], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [UseApi], + [UseResetPassword], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [Enabled], + [LicenseKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate], + [OwnersNotifiedOfAutoscaling], + [MaxAutoscaleSeats], + [UseKeyConnector], + [UseScim], + [UseCustomPermissions], + [UseSecretsManager], + [Status], + [UsePasswordManager], + [SmSeats], + [SmServiceAccounts], + [MaxAutoscaleSmSeats], + [MaxAutoscaleSmServiceAccounts], + [SecretsManagerBeta], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [LimitItemDeletion], + [UseOrganizationDomains], + [UseAdminSponsoredFamilies], + [SyncSeats], + [UseAutomaticUserConfirmation] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector, + @UseScim, + @UseCustomPermissions, + @UseSecretsManager, + @Status, + @UsePasswordManager, + @SmSeats, + @SmServiceAccounts, + @MaxAutoscaleSmSeats, + @MaxAutoscaleSmServiceAccounts, + @SecretsManagerBeta, + @LimitCollectionCreation, + @LimitCollectionDeletion, + @AllowAdminAccessToAllCollectionItems, + @UseRiskInsights, + @LimitItemDeletion, + @UseOrganizationDomains, + @UseAdminSponsoredFamilies, + @SyncSeats, + @UseAutomaticUserConfirmation + ); +END +GO + +-- Update Organization_Update stored procedure +CREATE OR ALTER PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT = null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = null, + @LimitCollectionDeletion BIT = null, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0, + @UseOrganizationDomains BIT = 0, + @UseAdminSponsoredFamilies BIT = 0, + @SyncSeats BIT = 0, + @UseAutomaticUserConfirmation BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Organization] + SET + [Identifier] = @Identifier, + [Name] = @Name, + [BusinessName] = @BusinessName, + [BusinessAddress1] = @BusinessAddress1, + [BusinessAddress2] = @BusinessAddress2, + [BusinessAddress3] = @BusinessAddress3, + [BusinessCountry] = @BusinessCountry, + [BusinessTaxNumber] = @BusinessTaxNumber, + [BillingEmail] = @BillingEmail, + [Plan] = @Plan, + [PlanType] = @PlanType, + [Seats] = @Seats, + [MaxCollections] = @MaxCollections, + [UsePolicies] = @UsePolicies, + [UseSso] = @UseSso, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseEvents] = @UseEvents, + [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, + [UseApi] = @UseApi, + [UseResetPassword] = @UseResetPassword, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [Enabled] = @Enabled, + [LicenseKey] = @LicenseKey, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [TwoFactorProviders] = @TwoFactorProviders, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector, + [UseScim] = @UseScim, + [UseCustomPermissions] = @UseCustomPermissions, + [UseSecretsManager] = @UseSecretsManager, + [Status] = @Status, + [UsePasswordManager] = @UsePasswordManager, + [SmSeats] = @SmSeats, + [SmServiceAccounts] = @SmServiceAccounts, + [MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats, + [MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts, + [SecretsManagerBeta] = @SecretsManagerBeta, + [LimitCollectionCreation] = @LimitCollectionCreation, + [LimitCollectionDeletion] = @LimitCollectionDeletion, + [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems, + [UseRiskInsights] = @UseRiskInsights, + [LimitItemDeletion] = @LimitItemDeletion, + [UseOrganizationDomains] = @UseOrganizationDomains, + [UseAdminSponsoredFamilies] = @UseAdminSponsoredFamilies, + [SyncSeats] = @SyncSeats, + [UseAutomaticUserConfirmation] = @UseAutomaticUserConfirmation + WHERE + [Id] = @Id; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadAbilities] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [UseEvents], + [Use2fa], + CASE + WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN + 1 + ELSE + 0 + END AS [Using2fa], + [UsersGetPremium], + [UseCustomPermissions], + [UseSso], + [UseKeyConnector], + [UseScim], + [UseResetPassword], + [UsePolicies], + [Enabled], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [LimitItemDeletion], + [UseOrganizationDomains], + [UseAdminSponsoredFamilies], + [UseAutomaticUserConfirmation] + FROM + [dbo].[Organization] +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + OU.[Id] OrganizationUserId, + O.[Name], + O.[Enabled], + O.[PlanType], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[UseSecretsManager], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + OU.[Key], + OU.[ResetPasswordKey], + O.[PublicKey], + O.[PrivateKey], + OU.[Status], + OU.[Type], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + PO.[ProviderId], + P.[Name] ProviderName, + P.[Type] ProviderType, + SS.[Enabled] SsoEnabled, + SS.[Data] SsoConfig, + OS.[FriendlyName] FamilySponsorshipFriendlyName, + OS.[LastSyncDate] FamilySponsorshipLastSyncDate, + OS.[ToDelete] FamilySponsorshipToDelete, + OS.[ValidUntil] FamilySponsorshipValidUntil, + OU.[AccessSecretsManager], + O.[UsePasswordManager], + O.[SmSeats], + O.[SmServiceAccounts], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + O.[LimitItemDeletion], + O.[UseAdminSponsoredFamilies], + O.[UseOrganizationDomains], + OS.[IsAdminInitiated], + O.[UseAutomaticUserConfirmation] +FROM + [dbo].[OrganizationUser] OU + LEFT JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] + LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] + LEFT JOIN + [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] + LEFT JOIN + [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] + LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] + LEFT JOIN + [dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id] + +GO + +CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] +AS +SELECT + PU.[UserId], + PO.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + PO.[Key], + O.[PublicKey], + O.[PrivateKey], + PU.[Status], + PU.[Type], + PO.[ProviderId], + PU.[Id] ProviderUserId, + P.[Name] ProviderName, + O.[PlanType], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + O.[UseAdminSponsoredFamilies], + P.[Type] ProviderType, + O.[LimitItemDeletion], + O.[UseOrganizationDomains], + O.[UseAutomaticUserConfirmation] +FROM + [dbo].[ProviderUser] PU + INNER JOIN + [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId] + INNER JOIN + [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId] + INNER JOIN + [dbo].[Provider] P ON P.[Id] = PU.[ProviderId] +GO + +EXEC sp_refreshview '[dbo].[OrganizationView]'; +EXEC sp_refreshview '[dbo].[OrganizationCipherDetailsCollectionsView]'; +EXEC sp_refreshview '[dbo].[ProviderOrganizationOrganizationDetailsView]'; +GO diff --git a/util/Migrator/DbScripts/2025-10-13_00_UserCryptoV2.sql b/util/Migrator/DbScripts/2025-10-13_00_UserCryptoV2.sql new file mode 100644 index 0000000000..8a5373be11 --- /dev/null +++ b/util/Migrator/DbScripts/2025-10-13_00_UserCryptoV2.sql @@ -0,0 +1,396 @@ +IF OBJECT_ID('[dbo].[UserSignatureKeyPair]') IS NULL +BEGIN + CREATE TABLE [dbo].[UserSignatureKeyPair] + ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [UserId] UNIQUEIDENTIFIER NOT NULL, + [SignatureAlgorithm] TINYINT NOT NULL, + [SigningKey] VARCHAR(MAX) NOT NULL, + [VerifyingKey] VARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_UserSignatureKeyPair] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_UserSignatureKeyPair_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE + ); +END +GO + +IF NOT EXISTS(SELECT name +FROM sys.indexes +WHERE name = 'IX_UserSignatureKeyPair_UserId') +BEGIN +CREATE UNIQUE NONCLUSTERED INDEX [IX_UserSignatureKeyPair_UserId] + ON [dbo].[UserSignatureKeyPair]([UserId] ASC); +END +GO + + +CREATE OR ALTER VIEW [dbo].[UserSignatureKeyPairView] +AS +SELECT + * +FROM + [dbo].[UserSignatureKeyPair] +GO + +CREATE OR ALTER PROCEDURE [dbo].[UserSignatureKeyPair_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON; + + SELECT + * + FROM + [dbo].[UserSignatureKeyPairView] + WHERE + [UserId] = @UserId; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[UserSignatureKeyPair_UpdateForRotation] + @UserId UNIQUEIDENTIFIER, + @SignatureAlgorithm TINYINT, + @SigningKey VARCHAR(MAX), + @VerifyingKey VARCHAR(MAX), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON; + + UPDATE + [dbo].[UserSignatureKeyPair] + SET + [SignatureAlgorithm] = @SignatureAlgorithm, + [SigningKey] = @SigningKey, + [VerifyingKey] = @VerifyingKey, + [RevisionDate] = @RevisionDate + WHERE + [UserId] = @UserId; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[UserSignatureKeyPair_SetForRotation] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @SignatureAlgorithm TINYINT, + @SigningKey VARCHAR(MAX), + @VerifyingKey VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON; + + INSERT INTO [dbo].[UserSignatureKeyPair] + ( + [Id], + [UserId], + [SignatureAlgorithm], + [SigningKey], + [VerifyingKey], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @UserId, + @SignatureAlgorithm, + @SigningKey, + @VerifyingKey, + @CreationDate, + @RevisionDate + ) +END +GO + +IF COL_LENGTH('[dbo].[User]', 'SecurityState') IS NULL +BEGIN + ALTER TABLE + [dbo].[User] + ADD + [SecurityState] VARCHAR(MAX) NULL; +END +GO + +IF COL_LENGTH('[dbo].[User]', 'SecurityVersion') IS NULL +BEGIN + ALTER TABLE + [dbo].[User] + ADD + [SecurityVersion] INT NULL; +END +GO + +IF COL_LENGTH('[dbo].[User]', 'SignedPublicKey') IS NULL +BEGIN + ALTER TABLE + [dbo].[User] + ADD + [SignedPublicKey] VARCHAR(MAX) NULL; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT = 0, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) = NULL, + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1, + @SecurityState VARCHAR(MAX) = NULL, + @SecurityVersion INT = NULL, + @SignedPublicKey VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[User] + ( + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [Kdf], + [KdfIterations], + [CreationDate], + [RevisionDate], + [ApiKey], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor], + [KdfMemory], + [KdfParallelism], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate], + [VerifyDevices], + [SecurityState], + [SecurityVersion], + [SignedPublicKey] + ) + VALUES + ( + @Id, + @Name, + @Email, + @EmailVerified, + @MasterPassword, + @MasterPasswordHint, + @Culture, + @SecurityStamp, + @TwoFactorProviders, + @TwoFactorRecoveryCode, + @EquivalentDomains, + @ExcludedGlobalEquivalentDomains, + @AccountRevisionDate, + @Key, + @PublicKey, + @PrivateKey, + @Premium, + @PremiumExpirationDate, + @RenewalReminderDate, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @LicenseKey, + @Kdf, + @KdfIterations, + @CreationDate, + @RevisionDate, + @ApiKey, + @ForcePasswordReset, + @UsesKeyConnector, + @FailedLoginCount, + @LastFailedLoginDate, + @AvatarColor, + @KdfMemory, + @KdfParallelism, + @LastPasswordChangeDate, + @LastKdfChangeDate, + @LastKeyRotationDate, + @LastEmailChangeDate, + @VerifyDevices, + @SecurityState, + @SecurityVersion, + @SignedPublicKey + ) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7), + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1, + @SecurityState VARCHAR(MAX) = NULL, + @SecurityVersion INT = NULL, + @SignedPublicKey VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [KdfMemory] = @KdfMemory, + [KdfParallelism] = @KdfParallelism, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesKeyConnector] = @UsesKeyConnector, + [FailedLoginCount] = @FailedLoginCount, + [LastFailedLoginDate] = @LastFailedLoginDate, + [AvatarColor] = @AvatarColor, + [LastPasswordChangeDate] = @LastPasswordChangeDate, + [LastKdfChangeDate] = @LastKdfChangeDate, + [LastKeyRotationDate] = @LastKeyRotationDate, + [LastEmailChangeDate] = @LastEmailChangeDate, + [VerifyDevices] = @VerifyDevices, + [SecurityState] = @SecurityState, + [SecurityVersion] = @SecurityVersion, + [SignedPublicKey] = @SignedPublicKey + WHERE + [Id] = @Id +END +GO + +EXECUTE sp_refreshview 'dbo.UserView' +EXECUTE sp_refreshview 'dbo.EmergencyAccessDetailsView' +EXECUTE sp_refreshview 'dbo.OrganizationUserUserDetailsView' +EXECUTE sp_refreshview 'dbo.ProviderUserUserDetailsView' +EXECUTE sp_refreshview 'dbo.UserEmailDomainView' +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2025-10-15_00_OrgUserConfirmById.sql b/util/Migrator/DbScripts/2025-10-15_00_OrgUserConfirmById.sql new file mode 100644 index 0000000000..a64cd1401b --- /dev/null +++ b/util/Migrator/DbScripts/2025-10-15_00_OrgUserConfirmById.sql @@ -0,0 +1,28 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ConfirmById] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + DECLARE @RowCount INT; + + UPDATE + [dbo].[OrganizationUser] + SET + [Status] = 2, -- Set to Confirmed + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id + AND [Status] = 1 -- Only update if status is Accepted + + SET @RowCount = @@ROWCOUNT; + + IF @RowCount > 0 + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END + + SELECT @RowCount; +END diff --git a/util/Migrator/DbUpLogger.cs b/util/Migrator/DbUpLogger.cs index 2587ce4d80..3f94990ba9 100644 --- a/util/Migrator/DbUpLogger.cs +++ b/util/Migrator/DbUpLogger.cs @@ -15,31 +15,31 @@ public class DbUpLogger : IUpgradeLog public void LogTrace(string format, params object[] args) { - _logger.LogTrace(Constants.BypassFiltersEventId, format, args); + _logger.LogTrace(Constants.BypassFiltersEventId, "{TraceMessage}", string.Format(format, args)); } public void LogDebug(string format, params object[] args) { - _logger.LogDebug(Constants.BypassFiltersEventId, format, args); + _logger.LogDebug(Constants.BypassFiltersEventId, "{DebugMessage}", string.Format(format, args)); } public void LogInformation(string format, params object[] args) { - _logger.LogInformation(Constants.BypassFiltersEventId, format, args); + _logger.LogInformation(Constants.BypassFiltersEventId, "{InfoMessage}", string.Format(format, args)); } public void LogWarning(string format, params object[] args) { - _logger.LogWarning(Constants.BypassFiltersEventId, format, args); + _logger.LogWarning(Constants.BypassFiltersEventId, "{WarningMessage}", string.Format(format, args)); } public void LogError(string format, params object[] args) { - _logger.LogError(Constants.BypassFiltersEventId, format, args); + _logger.LogError(Constants.BypassFiltersEventId, "{ErrorMessage}", string.Format(format, args)); } public void LogError(Exception ex, string format, params object[] args) { - _logger.LogError(Constants.BypassFiltersEventId, ex, format, args); + _logger.LogError(Constants.BypassFiltersEventId, ex, "{ErrorMessage}", string.Format(format, args)); } } diff --git a/util/MySqlMigrations/Migrations/20251009152659_CreatingSecretVersionTables.Designer.cs b/util/MySqlMigrations/Migrations/20251009152659_CreatingSecretVersionTables.Designer.cs new file mode 100644 index 0000000000..8683a835b5 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251009152659_CreatingSecretVersionTables.Designer.cs @@ -0,0 +1,3347 @@ +// +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("20251009152659_CreatingSecretVersionTables")] + partial class CreatingSecretVersionTables + { + /// + 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("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("EditorServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VersionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ArchivedDate") + .HasColumnType("datetime(6)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.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/20251009152659_CreatingSecretVersionTables.cs b/util/MySqlMigrations/Migrations/20251009152659_CreatingSecretVersionTables.cs new file mode 100644 index 0000000000..7e3ae2eebe --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251009152659_CreatingSecretVersionTables.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class CreatingSecretVersionTables : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SecretVersion", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + SecretId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Value = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + VersionDate = table.Column(type: "datetime(6)", nullable: false), + EditorServiceAccountId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + EditorOrganizationUserId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_SecretVersion", x => x.Id); + table.ForeignKey( + name: "FK_SecretVersion_OrganizationUser_EditorOrganizationUserId", + column: x => x.EditorOrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_SecretVersion_Secret_SecretId", + column: x => x.SecretId, + principalTable: "Secret", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SecretVersion_ServiceAccount_EditorServiceAccountId", + column: x => x.EditorServiceAccountId, + principalTable: "ServiceAccount", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorOrganizationUserId", + table: "SecretVersion", + column: "EditorOrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorServiceAccountId", + table: "SecretVersion", + column: "EditorServiceAccountId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_SecretId", + table: "SecretVersion", + column: "SecretId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SecretVersion"); + } +} diff --git a/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.Designer.cs b/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.Designer.cs new file mode 100644 index 0000000000..e6549a3b82 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.Designer.cs @@ -0,0 +1,3287 @@ +// +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("20251010142240_AddAutoConfirmUserToOrg")] + partial class AddAutoConfirmUserToOrg + { + /// + 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("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ArchivedDate") + .HasColumnType("datetime(6)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.cs b/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.cs new file mode 100644 index 0000000000..d0141d2b78 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251010142240_AddAutoConfirmUserToOrg.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class AddAutoConfirmUserToOrg : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization"); + } +} diff --git a/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.Designer.cs b/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.Designer.cs new file mode 100644 index 0000000000..a0aa7ef682 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.Designer.cs @@ -0,0 +1,3338 @@ +// +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("20251013083703_UserCryptoV2")] + partial class UserCryptoV2 + { + /// + 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("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SecurityState") + .HasColumnType("longtext"); + + b.Property("SecurityVersion") + .HasColumnType("int"); + + b.Property("SignedPublicKey") + .HasColumnType("longtext"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SignatureAlgorithm") + .HasColumnType("tinyint unsigned"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ArchivedDate") + .HasColumnType("datetime(6)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.cs b/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.cs new file mode 100644 index 0000000000..9811e401dd --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251013083703_UserCryptoV2.cs @@ -0,0 +1,84 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class UserCryptoV2 : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecurityState", + table: "User", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "SecurityVersion", + table: "User", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "SignedPublicKey", + table: "User", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "UserSignatureKeyPair", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + UserId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + SignatureAlgorithm = table.Column(type: "tinyint unsigned", nullable: false), + VerifyingKey = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + SigningKey = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false), + RevisionDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSignatureKeyPair", x => x.Id); + table.ForeignKey( + name: "FK_UserSignatureKeyPair_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_UserSignatureKeyPair_UserId", + table: "UserSignatureKeyPair", + column: "UserId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserSignatureKeyPair"); + + migrationBuilder.DropColumn( + name: "SecurityState", + table: "User"); + + migrationBuilder.DropColumn( + name: "SecurityVersion", + table: "User"); + + migrationBuilder.DropColumn( + name: "SignedPublicKey", + table: "User"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index dce61f805c..78a9433c53 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -220,6 +220,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("UseApi") .HasColumnType("tinyint(1)"); + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("tinyint(1)"); + b.Property("UseCustomPermissions") .HasColumnType("tinyint(1)"); @@ -1873,6 +1876,15 @@ namespace Bit.MySqlMigrations.Migrations .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"); @@ -1901,6 +1913,40 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -2159,6 +2205,42 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -2912,6 +2994,17 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -2984,6 +3077,31 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -3255,6 +3373,8 @@ namespace Bit.MySqlMigrations.Migrations { b.Navigation("GroupAccessPolicies"); + b.Navigation("SecretVersions"); + b.Navigation("ServiceAccountAccessPolicies"); b.Navigation("UserAccessPolicies"); diff --git a/util/PostgresMigrations/Migrations/20251009152612_CreatingSecretVersionTables.Designer.cs b/util/PostgresMigrations/Migrations/20251009152612_CreatingSecretVersionTables.Designer.cs new file mode 100644 index 0000000000..438f707f20 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251009152612_CreatingSecretVersionTables.Designer.cs @@ -0,0 +1,3353 @@ +// +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("20251009152612_CreatingSecretVersionTables")] + partial class CreatingSecretVersionTables + { + /// + 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("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("EditorServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ArchivedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.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/20251009152612_CreatingSecretVersionTables.cs b/util/PostgresMigrations/Migrations/20251009152612_CreatingSecretVersionTables.cs new file mode 100644 index 0000000000..36036dd1c5 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251009152612_CreatingSecretVersionTables.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class CreatingSecretVersionTables : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SecretVersion", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + SecretId = table.Column(type: "uuid", nullable: false), + Value = table.Column(type: "text", nullable: false), + VersionDate = table.Column(type: "timestamp with time zone", nullable: false), + EditorServiceAccountId = table.Column(type: "uuid", nullable: true), + EditorOrganizationUserId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SecretVersion", x => x.Id); + table.ForeignKey( + name: "FK_SecretVersion_OrganizationUser_EditorOrganizationUserId", + column: x => x.EditorOrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_SecretVersion_Secret_SecretId", + column: x => x.SecretId, + principalTable: "Secret", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SecretVersion_ServiceAccount_EditorServiceAccountId", + column: x => x.EditorServiceAccountId, + principalTable: "ServiceAccount", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorOrganizationUserId", + table: "SecretVersion", + column: "EditorOrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorServiceAccountId", + table: "SecretVersion", + column: "EditorServiceAccountId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_SecretId", + table: "SecretVersion", + column: "SecretId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SecretVersion"); + } +} diff --git a/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.Designer.cs b/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.Designer.cs new file mode 100644 index 0000000000..71f1645b52 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.Designer.cs @@ -0,0 +1,3293 @@ +// +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("20251010142249_AddAutoConfirmUserToOrg")] + partial class AddAutoConfirmUserToOrg + { + /// + 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("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ArchivedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.cs b/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.cs new file mode 100644 index 0000000000..f2b793951e --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251010142249_AddAutoConfirmUserToOrg.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class AddAutoConfirmUserToOrg : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization"); + } +} diff --git a/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.Designer.cs b/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.Designer.cs new file mode 100644 index 0000000000..0fc48fc5e5 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.Designer.cs @@ -0,0 +1,3344 @@ +// +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("20251013083502_UserCryptoV2")] + partial class UserCryptoV2 + { + /// + 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("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SecurityState") + .HasColumnType("text"); + + b.Property("SecurityVersion") + .HasColumnType("integer"); + + b.Property("SignedPublicKey") + .HasColumnType("text"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SignatureAlgorithm") + .HasColumnType("smallint"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ArchivedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.cs b/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.cs new file mode 100644 index 0000000000..3e9ddfe831 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251013083502_UserCryptoV2.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class UserCryptoV2 : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecurityState", + table: "User", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "SecurityVersion", + table: "User", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "SignedPublicKey", + table: "User", + type: "text", + nullable: true); + + migrationBuilder.CreateTable( + name: "UserSignatureKeyPair", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false), + SignatureAlgorithm = table.Column(type: "smallint", nullable: false), + VerifyingKey = table.Column(type: "text", nullable: false), + SigningKey = table.Column(type: "text", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false), + RevisionDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSignatureKeyPair", x => x.Id); + table.ForeignKey( + name: "FK_UserSignatureKeyPair_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserSignatureKeyPair_UserId", + table: "UserSignatureKeyPair", + column: "UserId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserSignatureKeyPair"); + + migrationBuilder.DropColumn( + name: "SecurityState", + table: "User"); + + migrationBuilder.DropColumn( + name: "SecurityVersion", + table: "User"); + + migrationBuilder.DropColumn( + name: "SignedPublicKey", + table: "User"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index c6ed007410..db34ccd7d0 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -222,6 +222,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("UseApi") .HasColumnType("boolean"); + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("boolean"); + b.Property("UseCustomPermissions") .HasColumnType("boolean"); @@ -1879,6 +1882,15 @@ namespace Bit.PostgresMigrations.Migrations .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"); @@ -1907,6 +1919,40 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -2165,6 +2211,42 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -2918,6 +3000,17 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -2990,6 +3083,31 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -3261,6 +3379,8 @@ namespace Bit.PostgresMigrations.Migrations { b.Navigation("GroupAccessPolicies"); + b.Navigation("SecretVersions"); + b.Navigation("ServiceAccountAccessPolicies"); b.Navigation("UserAccessPolicies"); diff --git a/util/Seeder/Factories/UserSeeder.cs b/util/Seeder/Factories/UserSeeder.cs index 12ac824cfd..389e807656 100644 --- a/util/Seeder/Factories/UserSeeder.cs +++ b/util/Seeder/Factories/UserSeeder.cs @@ -101,5 +101,4 @@ public class UserSeeder(RustSdkService sdkService, IPasswordHasher +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("20251009152635_CreatingSecretVersionTables")] + partial class CreatingSecretVersionTables + { + /// + 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("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("EditorServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VersionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ArchivedDate") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.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/20251009152635_CreatingSecretVersionTables.cs b/util/SqliteMigrations/Migrations/20251009152635_CreatingSecretVersionTables.cs new file mode 100644 index 0000000000..0735092023 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251009152635_CreatingSecretVersionTables.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class CreatingSecretVersionTables : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SecretVersion", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + SecretId = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: false), + VersionDate = table.Column(type: "TEXT", nullable: false), + EditorServiceAccountId = table.Column(type: "TEXT", nullable: true), + EditorOrganizationUserId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SecretVersion", x => x.Id); + table.ForeignKey( + name: "FK_SecretVersion_OrganizationUser_EditorOrganizationUserId", + column: x => x.EditorOrganizationUserId, + principalTable: "OrganizationUser", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_SecretVersion_Secret_SecretId", + column: x => x.SecretId, + principalTable: "Secret", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SecretVersion_ServiceAccount_EditorServiceAccountId", + column: x => x.EditorServiceAccountId, + principalTable: "ServiceAccount", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorOrganizationUserId", + table: "SecretVersion", + column: "EditorOrganizationUserId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_EditorServiceAccountId", + table: "SecretVersion", + column: "EditorServiceAccountId"); + + migrationBuilder.CreateIndex( + name: "IX_SecretVersion_SecretId", + table: "SecretVersion", + column: "SecretId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SecretVersion"); + } +} diff --git a/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.Designer.cs b/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.Designer.cs new file mode 100644 index 0000000000..4290c5d93c --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.Designer.cs @@ -0,0 +1,3276 @@ +// +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("20251010142244_AddAutoConfirmUserToOrg")] + partial class AddAutoConfirmUserToOrg + { + /// + 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("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ArchivedDate") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.cs b/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.cs new file mode 100644 index 0000000000..7c4d9effbd --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251010142244_AddAutoConfirmUserToOrg.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class AddAutoConfirmUserToOrg : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseAutomaticUserConfirmation", + table: "Organization"); + } +} diff --git a/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.Designer.cs b/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.Designer.cs new file mode 100644 index 0000000000..3ec01ef19f --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.Designer.cs @@ -0,0 +1,3327 @@ +// +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("20251013083514_UserCryptoV2")] + partial class UserCryptoV2 + { + /// + 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("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .HasColumnType("INTEGER"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ArchivedDate") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.cs b/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.cs new file mode 100644 index 0000000000..d6c09d3a13 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251013083514_UserCryptoV2.cs @@ -0,0 +1,79 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class UserCryptoV2 : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecurityState", + table: "User", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "SecurityVersion", + table: "User", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "SignedPublicKey", + table: "User", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "UserSignatureKeyPair", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "TEXT", nullable: false), + SignatureAlgorithm = table.Column(type: "INTEGER", nullable: false), + VerifyingKey = table.Column(type: "TEXT", nullable: false), + SigningKey = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false), + RevisionDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSignatureKeyPair", x => x.Id); + table.ForeignKey( + name: "FK_UserSignatureKeyPair_User_UserId", + column: x => x.UserId, + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserSignatureKeyPair_UserId", + table: "UserSignatureKeyPair", + column: "UserId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserSignatureKeyPair"); + + migrationBuilder.DropColumn( + name: "SecurityState", + table: "User"); + + migrationBuilder.DropColumn( + name: "SecurityVersion", + table: "User"); + + migrationBuilder.DropColumn( + name: "SignedPublicKey", + table: "User"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 494431b932..12b97386be 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -215,6 +215,9 @@ namespace Bit.SqliteMigrations.Migrations b.Property("UseApi") .HasColumnType("INTEGER"); + b.Property("UseAutomaticUserConfirmation") + .HasColumnType("INTEGER"); + b.Property("UseCustomPermissions") .HasColumnType("INTEGER"); @@ -1862,6 +1865,15 @@ namespace Bit.SqliteMigrations.Migrations .HasMaxLength(50) .HasColumnType("TEXT"); + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + b.Property("Storage") .HasColumnType("INTEGER"); @@ -1890,6 +1902,40 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -2148,6 +2194,42 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -2901,6 +2983,17 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -2973,6 +3066,31 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -3244,6 +3362,8 @@ namespace Bit.SqliteMigrations.Migrations { b.Navigation("GroupAccessPolicies"); + b.Navigation("SecretVersions"); + b.Navigation("ServiceAccountAccessPolicies"); b.Navigation("UserAccessPolicies");